跳转至

2021

通过 ipmitool 配置 iLO 4 管理端口

ipmitool 自带了对 iDRAC 的支持,可以通过 ipmitool delloem 设置 iDRAC 的管理端口。但是对 iLO 的支持并没有实现。研究了一番,找到了通过 raw command 配置 iLO 4 管理端口的方法。

这篇文章 讲述了 ipmitool lan 命令实际会发送的命令:

读取配置:

$ ipmitool raw 0x0c 0x02 CHANNEL KEY SET BLOCK

一般来说 SET 和 BLOCK 都是 0。KEY 的常见取值:

  • 3: IP 地址
  • 4: IP 地址来源
  • 5: MAC 地址
  • 6: 子网掩码
  • 12: 默认网关

返回的数据中,第一个字节忽略,剩下的就是数据了。

写入配置:

$ ipmitool raw 0x0c 0x01 CHANNEL KEY DATA...

知道如何读取配置后,接下来就是找到 iLO 4 配置 NIC 的地方了。一番搜索,找到了 HPE iLO IPMI User Guide。在第 101 页,可以找到一个用于配置 iLO NIC 选择的设置:

Index: 224
iLO Dedicated/Shared NIC Selection.
data 3:
• Selected iLO NIC.
◦ 0h = iLO Dedicated NIC is selected.
◦ 1h = iLO Shared NIC is selected.
◦ All others = reserved
• To switch to another iLO NIC:
1. Write this (and possibly parameter 197) to the desired NIC selection
2. Configure all other relevant network parameters for the desin
3. Reset iLO. The desired NIC will be in use after iLO reset.
• When writing changes to data 3, NIC selection:
◦ data 1 must be AAh
◦ data 2 must be 55h
◦ data 4 must be FFh

有这样的信息以后,可以通过下面的命令来设置 Shared NIC:

$ ipmitool raw 0x0c 0x01 0x01 224 0xAA 0x55 0x01 0xFF

再读出来 224,发现它的 data 4 表示 iLO reset needed for some settings changes that have been made。于是,执行 ipmitool mc reset warm 之后,就可以看到 NIC 选择已经更新:

$ ipmitool raw 0x0c 0x02 0x01 197 0x00 0x00
11 02 01 02

数据分别表示:

  • 0x02: Shared NIC Selection = ALOM
  • 0x01: Shared NIC Port Number = Port 1
  • 0x02: Platform supports ALOM shared NIC

如果想要的端口和默认选择不一样,可以写入 197 来更新。详见上面的文档链接。

超微的机器也有类似的办法:https://www.supermicro.org.cn/support/faqs/faq.cfm?faq=15868,可以用 ipmiutil smcoem lanport 命令来读取/修改。

Update:我给 IPMITOOL 提交了 PR,来简化这个过程

静态编译 ipmitool

为了在 ESXi 上运行 ipmitool,需要静态编译 ipmitool。网上已经有一些解决方案:

https://github.com/ryanbarrie/ESXI-ipmitool https://github.com/hobbsh/static-ipmitool https://github.com/ewenmcneill/docker-build-static-ipmitool

我稍微修改了一下,用来编译最新 ipmitool:

#!/bin/bash
set -x
export VERSION=1.8.18
rm -rf ipmitool_$VERSION
curl -L -o ipmitool_$VERSION.tar.bz2 http://deb.debian.org/debian/pool/main/i/ipmitool/ipmitool_$VERSION.orig.tar.bz2
tar xvf ipmitool_$VERSION.tar.bz2
cd ipmitool-$VERSION
CC=gcc CFLAGS=-m64 LDFLAGS=-static ./configure
make -j24
cd src
../libtool --silent --tag=CC --mode=link gcc -m64 -fno-strict-aliasing -Wreturn-type -all-static -o ipmitool.static ipmitool.o ipmishell.o ../lib/libipmitool.la plugins/libintf.la
file $PWD/ipmitool.static

复制下来,编译完成后 scp 到 esxi 中即可使用。

ESXi 网络配置

用过 ESXi 的大家都知道,它网页版对网络的配置功能有限,特别是 IPv6 的部分,有的事情无法实现。更好的办法是 SSH 到 ESXi 上直接用命令行进行配置。

可能会用到的一些命令:

  1. esxcfg-vmknic: 用来给 vmkernel 配置地址
  2. esxcfg-route: 设置系统路由表
  3. esxcli: 大杂烩,很多功能都在里面
  4. tcpdump-uw:魔改版 tcpdump

一些例子:

设置 IPv6 默认路由:

[root@esxi:~]esxcfg-route -f V6 -a default $IPV6

删除 vmkernel 的 IPv6 地址:

[root@esxi:~]esxcli network ip interface ipv6 address remove -i $VMKERNEL -I $IPV6/$PREFIX

参考:https://kb.vmware.com/s/article/1002662

Linksys E8450 OpenWRT 配置 w/ 802.11ax

背景

之前用的 newifi 路由器(Lenovo y1s)无线网总是出问题,于是换了一个新的支持 802.11ax 的路由器 Linksys E8450,目前在 openwrt snapshot 支持。Openwrt 的支持页面:Linksys E8450

过程

按照支持页面,下载固件:

$ wget https://downloads.openwrt.org/snapshots/targets/mediatek/mt7622/openwrt-mediatek-mt7622-linksys_e8450-squashfs-sysupgrade.bin

更新(2023-02-27):固件已经从 snapshot 进入正式版,下载链接为 https://downloads.openwrt.org/releases/22.03.3/targets/mediatek/mt7622/openwrt-22.03.3-mediatek-mt7622-linksys_e8450-squashfs-sysupgrade.bin。如果已经替换为 UBI,则使用 https://downloads.openwrt.org/releases/22.03.3/targets/mediatek/mt7622/openwrt-22.03.3-mediatek-mt7622-linksys_e8450-ubi-squashfs-sysupgrade.itb 固件。

然后访问固件升级页面:http://192.168.1.1/config-admin-firmware.html#firmware,选择下载的 bin 文件。点击“开始升级”,然后等待。一段时间后,ssh 到路由器:

$ ssh root@192.168.1.1
The authenticity of host '192.168.1.1 (192.168.1.1)' can't be established.
ED25519 key fingerprint is SHA256:REDACTED.
No matching host key fingerprint found in DNS.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.1' (ED25519) to the list of known hosts.


BusyBox v1.33.0 () built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt SNAPSHOT, r16242-41af8735d4
 -----------------------------------------------------
=== WARNING! =====================================
There is no root password defined on this device!
Use the "passwd" command to set up a new password
in order to prevent unauthorized SSH logins.
--------------------------------------------------
root@OpenWrt:~# uname -a
Linux OpenWrt 5.10.23 #0 SMP Wed Mar 17 19:55:38 2021 aarch64 GNU/Linux

配置 luci:

$ opkg update
$ opkg install luci

然后就可以网页访问看到 luci 了:Powered by LuCI Master (git-21.060.51374-cd06e70) / OpenWrt SNAPSHOT r16242-41af8735d4。

由于目前 luci 不支持 802.11ax 的配置,可以直接修改 uci 配置来达到效果:

root@OpenWrt:/# uci show wireless
root@OpenWrt:/# uci set wireless.radio1.htmode='HE80'
root@OpenWrt:/# /etc/init.d/network restart
'radio0' is disabled

注:实际上设置为 HE 开头的字符串即可,见 mac80211.sh

再连接上 Wi-Fi 的时候就可以看到是 802.11ax 模式了。也在 OpenWRT 论坛 上分享了一下这个方案。

更新(2021-07-31):目前最新的 luci 版本已经可以在网页上配置 802.11ax 模式了。

用 gitlab ci 构建并部署应用到 k8s

背景

在 k8s 集群中部署了 gitlab-runner,并且希望在 gitlab ci 构建完成后,把新的 docker image push 到 private repo,然后更新应用。

参考文档:Gitlab CI 与 Kubernetes 的结合Using Docker to build Docker images

在 gitlab ci 中构建 docker 镜像

这一步需要 DinD 来实现在容器中构建容器。为了达到这个目的,首先要在 gitlab-runner 的配置中添加一个 volume 来共享 DinD 的证书路径:

gitlabUrl: REDACTED
rbac:
  create: true
runnerRegistrationToken: REDACTED
runners:
  config: |
    [[runners]]
      [runners.kubernetes]
        image = "ubuntu:20.04"
        privileged = true
      [[runners.kubernetes.volumes.empty_dir]]
        name = "docker-certs"
        mount_path = "/certs/client"
        medium = "Memory"
  privileged: true

注意两点:1. privileged 2. 多出来的 volume

用 helm 部署 gitlab runner 之后,按照下面的方式配置 gitlab-ci:

image: docker:19.03.12

variables:
  DOCKER_HOST: tcp://docker:2376
  #
  # The 'docker' hostname is the alias of the service container as described at
  # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services.
  # If you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
  # the variable must be set to tcp://localhost:2376 because of how the
  # Kubernetes executor connects services to the job container
  # DOCKER_HOST: tcp://localhost:2376
  #
  # Specify to Docker where to create the certificates, Docker will
  # create them automatically on boot, and will create
  # `/certs/client` that will be shared between the service and job
  # container, thanks to volume mount from config.toml
  DOCKER_TLS_CERTDIR: "/certs"
  # These are usually specified by the entrypoint, however the
  # Kubernetes executor doesn't run entrypoints
  # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4125
  DOCKER_TLS_VERIFY: 1
  DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
  DOCKER_DAEMON_OPTIONS: "--insecure-registry=${REGISTRY}"

services:
  - name: docker:19.03.12-dind
    entrypoint: ["sh", "-c", "dockerd-entrypoint.sh $DOCKER_DAEMON_OPTIONS"]

before_script:
  # Wait until client certs are generated
  # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27384
  - until docker info; do sleep 1; done
  - echo "$REGISTRY_PASS" | docker login $REGISTRY --username $REGISTRY_USER --password-stdin

build:
  stage: build
  script: ./build.sh

这里有很多细节,包括 DinD 的访问方式,等待 client cert,设置 docker 的 insecure registry 和 login 等等。经过 @CircuitCoder 的不断摸索,终于写出了可以用的配置。

如此配置以后,就可以在 gitlab ci 的构建脚本里用 docker 来 build 并且 push 到自己的 registry 了。为了防止泄露密钥,建议把这些变量放到 gitlab ci 设置的 secrets 中。

自动部署到 k8s

为了让 k8s 重启一个 deployment,一般的做法是:

kubectl -n NAMESPACE rollout restart deployment/NAME

我们希望 gitlab ci 在 build 之后,去执行这一个命令,但又不希望提供太多的权限给 gitlab。所以,我们创建 Service Account 并设置最小权限:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: gitlab-test
  namespace: test
rules:
- verbs:
    - get
    - patch
  apiGroups:
    - 'apps'
  resources:
    - 'deployments'
  resourceNames:
    - 'test-deployment'

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: gitlab
  namespace: test
subjects:
  - kind: ServiceAccount
    name: gitlab
    namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: gitlab-test

要特别注意这几个配置的 namespace 的对应关系:Role 和 RoleBinding 需要放在同一个 ns 下。

接着,到 GitLab 的 Operations->Kubernetes 创建 cluster,把 service account 的 token 和 ca.crt 从 secret 里找到并贴到网页上。GitLab 会按照 Environment scope 匹配到 environment,如果某个 stage 的 environment 匹配上了,就会把 kube credentials 配置好。修改 gitlab-ci.yml:

deploy:
  stage: deploy
  image: bitnami/kubectl:1.20
  environment:
    name: production
  only:
    - master
  script:
    - kubectl -n test rollout restart deployment/test

这样就完成配置了。

Gnome 的 Fractional Scaling

背景

最近发现部分软件(包括 Google Chrome,Firefox 和 Visual Studio Code)在 125% 的 Fractional Scaling 模式下会很卡。找到了一些临时解决方法,但是很不优雅,也很麻烦。所以深入研究了一下 Fractional Scaling 的工作方式。

临时解决方法

根据关键字,找到了 Chrome menus too slow after enabling fractional scaling in Ubuntu 20.04。按它的方法,关闭 Google Chrome 的硬件加速,发现卡顿问题确实解决了。

类似地,也可以关闭 VSCode 的硬件加速,在 Firefox 里也可以找到相应的设置。这样操作确实可以解决问题。但是,对于每一个出问题的应用都这样搞一遍,还是挺麻烦的。

另一个思路是,不使用 Fractional Scaling,而只是把字体变大。但毕竟和我们想要的效果不大一样。

一些发现

在物理机进行了一些实验以后,发现一个现象:125% 的时候卡顿,而其他比例(100%,150%,175%,200%)都不卡顿。

网上一顿搜到,找到了 xrandr 工具。下面是观察到的一些现象(GNOME 设置分辨率一直是 1920x1080):

放缩比例 xrandr 显示的分辨率 xrandr 显示的 transform
100% 1920x1080 diag(1.0, 1.0, 1.0)
125% 3072x1728 diag(1.6, 1.6, 1.0)
150% 2560x1440 diag(1.33, 1.33, 1.0)
175% 2208x1242 diag(1.15, 1.15, 1.0)
200% 1920x1080 diag(1.0, 1.0, 1.0)

xrandr 文档 中,写了:transform 是一个 3x3 矩阵,矩阵乘以输出的点的坐标得到图形缓存里面的坐标。

由此可以猜想:fractional scaling 的工作方式是,把绘制的 buffer 调大,然后再用 transform 把最终输出分辨率调成 1920x1080。可以看到,xrandr 显示的分辨率除以 transform 对应的值,就是 1920x1080。但这并不能解释 100% 和 200% 的区别,所以肯定还漏了什么信息。

翻了翻 mutter 实现 fractional scaling 的 pr,找到了实现 scale 的一部分:

if (clutter_actor_get_resource_scale (priv->actor, &resource_scale) &&
    resource_scale != 1.0f)
  {
    float paint_scale = 1.0f / resource_scale;
    cogl_matrix_scale (&modelview, paint_scale, paint_scale, 1);
  }

然后找到了一段对 scale 做 ceiling 的代码

if (_clutter_actor_get_real_resource_scale (priv->actor, &resource_scale))
  {
    ceiled_resource_scale = ceilf (resource_scale);
    stage_width *= ceiled_resource_scale;
    stage_height *= ceiled_resource_scale;
  }

这样,100% 和其他比例就区分开了。

另外,也在代码 中发现:

#define SCALE_FACTORS_PER_INTEGER 4
#define SCALE_FACTORS_STEPS (1.0 / (float) SCALE_FACTORS_PER_INTEGER)
#define MINIMUM_SCALE_FACTOR 1.0f
#define MAXIMUM_SCALE_FACTOR 4.0f

这段代码规定了比例只能是 25% 的倍数。

我也试了一下用 xrandr --scale 1.5x1.5:效果就是窗口看起来都更小了,分辨率变成了 2880x1620,transform 是 diag(1.5, 1.5, 1.0)。

虚拟机测试

接着,用虚拟机做了一些测试。为了在 GNOME over Wayland 上使用 fractional scaling,需要运行:

$ gsettings set org.gnome.mutter experimental-features "['scale-monitor-framebuffer']"

接着又做了类似上面的测试(GNOME 设置分辨率一直是 2560x1600):

放缩比例 xrandr 显示的分辨率
100% 2560x1600
125% 2048x1280
150% 1704x1065
175% 1464x915
200% 1280x800

在这个测试中,xrandr 显示的 transform 一直都是单位矩阵;还用了来自 xyproto/wallutilswayinfo 命令查看输出的分辨率,一直是 2560x1600,DPI 一直是 96。用 wallutils 的 xinfo 看到的结果和 xrandr 一致(通过 XWayland)。但是和物理机有一点不同:物理机有一个选项问要不要打开 fractional scaling,下面还会提示性能下降的问题;但是虚拟机上并没有这个提示,而是直接给了一些 Scale 比例的选项。

尝试了一下,在 GNOME over X11 上是找不到 fractional scaling 的(没有出现设置 scale 的选项)。找到一个实现这个功能的 fork:https://github.com/puxplaying/mutter-x11-scaling,不过没有尝试过。

我也尝试在虚拟机中用 xrandr --scale,结果就是输出黑屏,需要重启 gdm 来恢复到登录界面。

更新:由于物理机使用的是 Ubuntu,想到是不是 Ubuntu 采用了上面那个 fork 的 patch,然后就在 changelog 中看到:

mutter (3.38.1-1ubuntu1) groovy; urgency=medium

  * Merge with debian, including new upstream version, remaining changes:
    - debian/gbp.conf: update upstream branch to point to ubuntu/master
    - debian/patches/x11-Add-support-for-fractional-scaling-using-Randr.patch:
      + X11: Add support for fractional scaling using Randr
  * d/p/clutter-backend-x11-Don-t-set-the-font-dpi-computed-on-X1.patch:
    - Dropped, applied upstream

也找到了对应的 patch 文件。这也就解释了,为什么网上会说 GNOME over X11 支持 fractional scaling,并且需要用 gsettings 打开,而我在 Debian 和 Arch Linux 上设置这个选项也没有用了。原来是 Ubuntu 加的私货啊。

在 patch 中,找到了这么一段配置的解释:

+    <key name="fractional-scale-mode" enum="org.gnome.mutter.X11.scale-mode">
+      <default>"scale-ui-down"</default>
+      <description>
+        Choose the scaling mode to be used under X11 via Randr extension.
+
+        Supported methods are:
+
+        • “scale-up”     — Scale everything up to the requested scale, shrinking
+                           the UI. The applications will look blurry when scaling
+                           at higher values and the resolution will be lowered.
+        • “scale-ui-down — Scale up the UI toolkits to the closest integer
+                           scaling value upwards, while scale down the display
+                           to match the requested scaling level.
+                           It increases the resolution of the logical display.
+      </description>
+    </key>

这样就可以解释前面看到的现象了:默认是 scale-ui-down,也就是先放大到两倍(closest integer scaling value upwards),再缩小(scale down the display to match the requested scaling level)。

通过 rook 在 k8s 上部署 ceph 集群

背景

为了方便集群的使用,想在 k8s 集群里部署一个 ceph 集群。

Ceph 介绍

Ceph 有这些组成部分:

  1. mon:monitor
  2. mgr:manager
  3. osd:storage
  4. mds(optional):用于 CephFS
  5. radosgw(optional:用于 Ceph Object Storage

配置

我们采用的是 rook 来部署 ceph 集群。

参考文档:https://rook.github.io/docs/rook/v1.5/ceph-examples.html

首先,克隆 rook 的仓库。建议选择一个 release 版本。

接着,运行下面的命令:

sudo apt install -y lvm2
# required
kubectl apply -f rook/cluster/examples/kubernetes/ceph/crds.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/common.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/operator.yaml
# debugging only
kubectl apply -f rook/cluster/examples/kubernetes/ceph/toolbox.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/direct-mount.yaml
# CephFS
kubectl apply -f rook/cluster/examples/kubernetes/ceph/filesystem.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/csi/cephfs/storageclass.yaml

前面三个 yaml 是必须的,toolbox 是用来查看 ceph 状态的,direct mount 是用来 mount cephfs 的,后两个是为了用 cephfs 的。

接着,按照自己的需求编辑 rook/cluster/exmaples/kuberenetes/ceph/cluster.yaml 然后应用。此时你的集群应该就已经起来了。

然后,可以进 toolbox 查看 ceph 状态

$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash

也可以进 direct-mount 容器查看 pv 路径

# get volume path of pvc
kubectl get pv -o custom-columns=NAME:.metadata.name,NAMSEPACE:.spec.claimRef.namespace,CLAIM:.spec.claimRef.name,PATH:.spec.csi.volumeAttributes.subvolumeName

kubectl -n rook-ceph exec -it deploy/rook-direct-mount -- bash
# in the pod
mkdir /tmp/registry
mon_endpoints=$(grep mon_host /etc/ceph/ceph.conf | awk '{print $3}')
my_secret=$(grep key /etc/ceph/keyring | awk '{print $3}')
mount -t ceph -o mds_namespace=myfs,name=admin,secret=$my_secret $mon_endpoints:/ /tmp/registry
df -h

cd /tmp/registry/volumes/csi/PATH

用 k3s 部署 k8s

背景

最近需要部署一个 k8s 集群,觉得之前配置 kubeadm 太繁琐了,想要找一个简单的。比较了一下 k0s 和 k3s,最后选择了 k3s。

配置

k3s 的好处就是配置十分简单:https://rancher.com/docs/k3s/latest/en/quick-start/。不需要装 docker,也不需要装 kubeadm。

  1. 在第一个 node 上跑:curl -sfL https://get.k3s.io | sh -
  2. 在第一个 node 上获取 token:cat /var/lib/rancher/k3s/server/node-token
  3. 在其他 node 上跑:curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=mynodetoken sh -

然后就搞定了。从第一个 node 的 /etc/rancher/k3s/k3s.yaml获取 kubectl 配置。

常用交换机命令

背景

最近接触了 Cisco,DELL,Huawei,H3C,Ruijie 的网络设备,发现配置方式各有不同,故记录一下各个厂家的命令。

Huawei

测试型号:S5320

保存配置

<HUAWEI>save
The current configuration will be written to flash:/vrpcfg.zip.
Are you sure to continue?[Y/N]y
Now saving the current configuration to the slot 0....
Save the configuration successfully.

进入配置模式

<HUAWEI> system-view

查看当前配置

[HUAWEI] display current-configuration

查看 LLDP 邻居

[HUAWEI]display lldp neighbor brief

查看 CDP 邻居

[HUAWEI]display cdp neighbor brief

启用 LLDP

[HUAWEI]lldp enable

启用 CDP

[HUAWEI-XGigabitEthernet0/0/1]lldp compliance cdp txrx

启用只读 SNMPv1 community

[HUAWEI]snmp-agent sys-info version all
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
Warning: SNMPv1/SNMPv2c is not secure, and it is recommended to use SNMPv3.
[HUAWEI]snmp-agent community read [COMMUNITY NAME]
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y

启用 SNMP iso view

默认情况下 SNMP 会缺少一些标准的 MIB(比如 LLDP),可以打开 iso view:

[HUAWEI]snmp-agent mib-view included iso-view iso
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
[HUAWEI]snmp-agent community read [COMMUNITY NAME] mib-view iso-view

查看 ARP 表

[HUAWEI]display arp

ARPING

[HUAWEI]arp send-packet X.X.X.X ffff-ffff-ffff interface Vlanif VLAN

启用 STP 协议

[HUAWEI]stp enable
[HUAWEI]stp mode vbst

设置 NTP 服务器

[HUAWEI]ntp-service unicast-server x.x.x.x

设置远程 syslog 服务器

[HUAWEI]info-center loghost x.x.x.x

设置 LACP 链路聚合

[HUAWEI-XGigabitEthernet0/0/1]eth-trunk 1
[HUAWEI-XGigabitEthernet0/0/2]eth-trunk 1
[HUAWEI]interface Eth-Trunk 1
[HUAWEI-Eth-Trunk1]mode lacp

DELL

测试型号:N3048

保存配置

console#copy running-config startup-config

This operation may take few minutes.
Management interfaces will not be available during this time.

Are you sure you want to save? (y/n) y

Configuration Saved!

进入配置模式

console>enable
console# configure

查看当前配置

console# show running-config

查看 LLDP 邻居

console#show lldp remote-device all

VLAN Trunk 配置

console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode trunk
console(config-if-Gi1/0/1)#switchport trunk allowed vlan xxx,xxx-xxx

VLAN Access 配置

console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode access
console(config-if-Gi1/0/1)#switchport access vlan xxx

查看 VLAN 配置

console#show vlan

批量配置 interface

console(config)#interface range Gi1/0/1-4

启用 SSH 服务器

console(config)#crypto key generate rsa
console(config)#crypto key generate dsa
console(config)#ip ssh server

启用 CDP(DELL 称之为 ISDP)

console(config)#isdp enable

启用只读 SNMPv1 community

console(config)#snmp-server community [COMMUNITY NAME] ro

设置 NTP 服务器

console(config)#sntp unicast client enable
console(config)#sntp server x.x.x.x

设置 NTP 服务器

console(config)#sntp unicast client enable
console(config)#sntp server x.x.x.x

设置 STP 协议

console(config)#spanning-tree mode rapid-pvst

H3C

进入配置模式

<switch>system-view
System View: return to User View with Ctrl+Z.
[switch]

查看当前配置

[switch]display current-configuration

查看 lldp 邻居

[switch]display lldp neighbor-information

保存配置

[switch]save
The current configuration will be written to the device. Are you sure? [Y/N]:y
Please input the file name(*.cfg)[flash:/startup.cfg]
(To leave the existing filename unchanged, press the enter key):y
The file name is invalid(does not end with .cfg).

批量配置 interface

[switch]interface range GigabitEthernet 1/0/1 to GigabitEthernet 1/0/24
[switch-if-range]

查看 MAC 地址表

[switch]show mac-address

打开 LLDP 和 CDP

[switch]lldp global enable
[switch]lldp compliance cdp

Mellanox

进入配置模式

switch > enable
switch # configure terminal
switch (config) #

查看当前配置

switch (config) # show running-config

查看 interface 状态

switch (config) # show interfaces brief

查看以太网端口状态

switch (config) # show interfaces ethernet status

查看 lldp 邻居

switch (config) # show lldp remote

保存配置

switch (config) # configuration write

批量配置 interface

switch (config) # interface ethernet 1/1/1-1/1/4
switch (config interface ethernet 1/1/1-1/1/4) #

查看 MAC 地址表

switch (config) # show mac-address-table

查看链路聚合状态

switch (config) # show interfaces port-channel summary

把拆分的四个 SFP 口恢复成一个

switch (config interface ethernet 1/1/1) # module-type qsfp 

把一个 QSFP 口拆分成四个

switch (config interface ethernet 1/1) # shutdown
switch (config interface ethernet 1/1) # module-type qsfp-split-4

设置链路聚合

switch (config interface ethernet 1/1) # channel-group 1 mode active
switch (config interface ethernet 1/2) # channel-group 1 mode active

模式可以选择:active(LACP)/passive(LACP)/on(Static)

设置 STP 协议

switch (config) # spanning-tree mode rpvst

设置远程 syslog 服务器

switch (config) # logging x.x.x.x

设置 NTP 服务器

switch (config) # ntp server x.x.x.x

Cisco

设置 NTP 服务器

# ntp server x.x.x.x

配置 Trunk

# config terminal
(config)# interface ethernet 1/1
(config-if)# switchport mode trunk
(config-if)# switchport trunk allowed vlan 12-34

配置 Access

# config terminal
(config)# interface ethernet 1/1
(config-if)# switchport mode access
(config-if)# switchport access vlan 1234

PCB 笔记

记录一下在学习画板子过程中学到的心得。

工具

目前使用过 KiCadlceda

  • KiCad: 开源软件,跨平台。
  • lceda:在线编辑,不需要安装,和 lcsc 有深度集成。

项目 jiegec/HT42B534USB2UART 采用的是 KiCad 5 编写的。目前正在做的另一个项目采用 lceda

流程

  1. 选择所需要使用的芯片,查找芯片的 datasheet。
  2. 寻找采用了芯片的一些设计,特别是看 schematic。
  3. 按照 datasheet 里面推荐的电路,或者是其他人的设计,画自己需要的 schematic。
  4. 设置好各个元件的 footprint,然后转到 PCB 设计。
  5. 在 PCB 里面布线,生成 Gerber 等文件。
  6. 把 Gerber 给到生产商(比如 jlc),交付生产。
  7. 如果是自己焊接,则需要购买元件,比如从 lcsc 购买。
  8. 收到 PCB 和元件后,自己按照 BOM 和 schematic 焊接各个元件。

笔记

  1. 对于一些连接很多元件的信号,比如 GND,可以留作铺铜解决。也就是说,先不管 GND,把其他所有的信号都接好以后,再在顶层铺铜;如果还是有没有连接上的 GND,可以通过过孔(Via)走到底层,在底层再铺一层铜。
  2. 对于外部供电的 VCC 和 GND,在 KiCad 中需要用 PWR_FLAG 标记一下。
  3. 在 KiCad 中设计 PCB 前,要把生产商的工艺参数设置好,不然画了也要重画。
  4. lceda 在选择元件的时候,可以直接从 lcsc 里选择,这样可以保证封装和商品可以对得上,不需要手动进行匹配。
  5. 如果要用 jlc 的 SMT 贴片,先在 SMT 元件列表 里搜索所需要的元件;推荐用基本库,如果用其他库,则要加钱;选好元件以后,用元件编号去 lceda 里搜索并添加到 schematic。
  6. 对于涉及模拟信号的设计,比如音频,需要特别注意模拟信号的电和地都是单独的:AVCCAGND。所以要特别注意 datasheet 里面不同的地的表示方法。最后,再用磁珠把 VCCAVCCGNDAGND 分别连接起来就可以了。可以参考 DE2 板子中第 19 页的音频部分设计Staying well grounded
  7. 在 schematic 里经常会出现在电源附近的电容,那么,在 PCB 中,也尽量把这些电容放在对应的电源的旁边。
  8. 耳机插座里面,一般分三种组成部件:Tip,Ring,Sleeve。只有两段的是 TS,三段的是 TRS,四段的是 TRRS。TS 是单声道,T 是声音,S 是地。TRS 是双声道,T 是左声道(或者单声道),R 是右声道,S 是地。TRRS 则是双声道加录音。一般来说,LINE IN 是双声道,MIC IN 是单声道,它们的阻抗也不同;LINE OUT 和 HEADPHONE OUT 都是双声道,但 HEADPHONE OUT 经过了额外的放大器。
  9. 遇到一个 SPI 协议没有 SPI_MISO 引脚的芯片,可能说明它是 write-only 的。
  10. 手焊的基本元件,一般用 0603 加一些 Padding 的封装;SMT 的话,则建议用 0402 封装。
  11. I2C 的信号线一般需要加一个几 K 欧姆的上拉电阻到 VCC。

JLC SMT 的基础库不需要换料费,如何寻找基础库中的元件:

  1. 电阻品牌是 UNI-ROYAL,型号命名规则是:
    1. 封装:0603/0402
    2. 功率:WA/WG/W8
    3. 误差:F(1%)
    4. 阻值:三位整数 + 一位 exp(J 表示 -1,K 表示 -2,L 表示 -3),例如 2002 表示 200*10^2=20k,1003 表示 100*10^3=100k,3300 表示 330*10^0=330,330J 表示 330*10^-1=33,330K 表示 330*10^-2=3.3 例子:要找 0402 封装的 10k 欧电阻,搜索 0402WGF1003;要找 0603 封装的 33 欧电阻,搜索 0603WAF330。
  2. 电容品牌有风华/三星/国巨,三星的电容型号命名规则是:
    1. 封装:05(0402)/10(0603)
    2. 字母:A/B/C
    3. 电容:两位整数 + 一位 exp,单位是 pF,例如 105 表示 10*10^5pF=10^6pF=1uF,104 表示 10*10^4pF=10^5pF=0.1uF 例子:要找 0402 封装的 100nF 电容,搜索 CL05B104;要找 0603 封装的 1uF 电容,搜索 CL10A105。也可以只搜电容的数字部分,可以找到更多品牌。

阻抗匹配

在传输线上,如果出现阻抗变化,就会导致信号出现反射,质量变差。因此,需要保证传输线的两端和传输线整个过程的阻抗一致。

阻抗设置为多少,一般要看协议的规定。确定好协议定义的阻抗以后,需要查看信号两端的芯片内部的阻抗,如果和协议不一致,需要额外添加电阻,并且电阻要尽量放在接近芯片的位置上。由于传输线在 PCB 上,所以和 PCB 厂商的工艺有关,需要去厂商的阻抗计算器上进行计算,例如 jlc 阻抗计算器。涉及到的参数有:

  1. 板子层数:PCB 层数,最简单的正反面就是 2 层
  2. 成品厚度:整个 PCB 加起来的厚度,例如 1.6mm
  3. 内层铜厚:夹在内部的 PCB 的铜的厚度,例如 0.5 oz,就是 1.37/2=0.685 mil
  4. 外层铜厚:PCB 上下暴露在外面的两层的铜的厚度,常见 1 oz=1.37 mil
  5. 需求阻抗:协议所要求的阻抗,例如单端 50 欧姆(SDIO),差分 90 欧姆(USB)
  6. 阻抗模式:传输线的连接方式,见下(图源 KiCad)
    1. 单端阻抗(Microstrip Line):一根线传输信号,地线在另一个平面,图中上面的长方形就是传输线,底部就是地平面
    2. 差分阻抗(Coupled Microstrip Line):差分线传输信号,地线在另一个平面,图中上方两个长方形就是差分传输线,底部是地平面
    3. 共面单端:一根线传输信号,周围就是地平面
    4. 共面差分:差分线传输信号,周围就是地平面
  7. 阻抗层:传输线所在的层
  8. 参考层:地线所在的层

由于双层 PCB 的两层铜之间距离比较远(例如 57.68 mil),如果采用单端阻抗,那么需要比较大的线宽,例如用 jlc 阻抗计算器,50 欧姆阻抗需要 106.68 mil 的线宽。如果采用四层 PCB,最上面两层之间距离缩小了很多(例如 7.99 mil),此时即使用单端阻抗,用 jlc 计算得出只需要 13.2 mil 的线宽。所以双层 PCB 更适合使用共面单端的方式,此时传输线和地线放在了同一个平面,距离比较小,就不需要那么大的线宽。

这里的单位:1 mil = 0.0254 mm,1 inch = 1000 mil = 0.0254 m,1 oz = 1.37 mil = 0.0348 mm

iDRAC 各版本

iDRAC 版本

目前接触到的 iDRAC 版本有:7 8 9。一些常见的服务器型号和 iDRAC 版本对应关系:

基本上,如果是 PowerEdge R 什么的,就看第二位数字,就可以推断出版本号了。

下面列举了一下可能会用到的版本。

iDRAC 7

iDRAC 7 在 2020 年 2 月停止更新了,最新版本是 2.65.65.65。

升级路线参考:Reddit

iDRAC 8

iDRAC 9

  • 4.00.00.00: 下载页面,2019 年 12 月版本。LLDP 连接视图。
  • 4.22.00.00: 下载页面,2020 年 7 月版本。
  • 4.40.00.00: 下载页面,2020 年 12 月版本,下一代的 iDRAC virtual console 和 virtual media,支持 Infiniband。
  • 5.00.00.00: 下载页面,2021 年 6 月版本。
  • 7.00.00.00: 下载页面,2023 年 6 月版本。

在线升级

iDRAC 可以在线从 Dell 官网下载新版本升级,在网页上选择通过 HTTPS 升级,域名写 ,具体见文档

使用 SSSD 的 LDAP 认证

前言

最近在研究替换一个老的用户系统,于是顺便学习了一下 LDAP,还有 SSSD。LDAP 是一个目录协议,顺带的,因为用户信息也可以存在里面,所以也就成了一个常见的用户认证协议。SSSD 就是一个 daemon,把系统的 NSS PAM 的机制和 LDAP 连接起来。

配置

其实很简单,安装 sssd 并且配置即可:

$ sudo apt install sssd
$ sudo vim /etc/sssd/sssd.conf
# file content:
[sssd]
config_file_version = 2
services = nss,pam
domains = LDAP

[domain/LDAP]
cache_credentials = true
enumerate = true
entry_cache_timeout = 10
ldap_network_timeout = 2

id_provider = ldap
auth_provider = ldap
chpass_provider = ldap

ldap_uri = ldap://127.0.0.1/
ldap_chpass_uri = ldap://127.0.0.1/
ldap_search_base = dc=example,dc=com
ldap_default_bind_dn = cn=localhost,ou=machines,dc=example,dc=com
ldap_default_authtok = REDACTED
$ sudo systemctl enable --now sssd

一些字段需要按照实际情况编写,请参考sssd.confsssd-ldap

协议

那么,LDAP 里面的用户是如何和 Linux 里的用户对应起来的呢?可以看到,SSSD 会查询 posixAccount:

(&(objectclass=posixAccount)(uid=*)(uidNumber=*)(gidNumber=*))

然后,可以查到 posixAccount 的 schema,里面可以见到对应 /etc/passwd 的各个字段。相应的,也有 shadowAccount 对应 /etc/shadow

按照要求配好以后(建议用 ldapvi 工具),就可以用 getent passwd 看到新增的用户了。

上面的部分是通过 NSS 接口来查询的,除了用户以外,还有其他的一些 NIS 信息可以通过 LDAP 查询。此外,如果要登录的话,则是用 PAM 认证,SSSD 则会把 PAM 认证转换成 LDAP 的 Bind:

$ su test
Password:
# sssd: bind to dn of test user with password

如果 Bind 成功,则认为登录成功;否则就是登录失败。

如果用户要修改密码,SSSD 默认用的是 RFC3062 LDAP Password Modify Extended Operation 的方式;如果服务器不支持的话,可以按照 文档 使用 ldap modify 方式来修改密码。

SSD 还可以配置 sudo 支持,也是用类似的方法,添加 objectClass=sudoRole 的目录项即可。可以参考 man sudoers.ldap 编写对应的目录项。

对于 SSH 配置,可以参考 RedHat 的文档,和参考 man sss_ssh_authorizedkeys 配置 authorized keys 命令。然后,给用户添加 sshPublicKey 属性即可,内容与 ~/.ssh/id_*.pub 一致。

相关 RFC

LDAP-Related RFCs

在 Big Sur(M1) 上解决 LaTeX 找不到楷体字体的问题

背景

最近在尝试移植 MiKTeX 到 Apple Silicon 上,添加了一些 patch 以后就可以工作了,但遇到了新的问题,即找不到 KaiTi

~/Library/Application Support/MiKTeX/texmfs/install/tex/latex/ctex/fontset/ctex-fontset-macnew.def:99:
   Package fontspec Error:
      The font "Kaiti SC" cannot be found.

miktex-fc-list 命令找了一下,确实没有找到:

$ /Applications/MiKTeX\ Console.app/Contents/bin/miktex-fc-list | grep Kaiti
# Nothing

上网搜了一下,找到了一个解决方案:字体在目录 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Support/FontSubsets/Kaiti.ttc 里,所以手动安装一下,就可以让 LaTeX 找到了。但我觉得,与其安装多一份在文件系统里,不如让 LaTeX 去找它。

解决方法

按照上面的线索,找到了 Kaiti.ttc 所在的路径:

$ fd Kaiti.ttc
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc

可以看到,和上面的路径又不大一样。研究了一下 fontconfig,发现可以用 miktex-fc-conflist 找到配置文件的目录:

$ /Applications/MiKTeX\ Console.app/Contents/bin/miktex-fc-conflist
+ ~/Library/Application Support/MiKTeX/texmfs/config/fontconfig/config/localfonts2.conf: No description
+ ~/Library/Application Support/MiKTeX/texmfs/config/fontconfig/config/localfonts.conf: No description
...

看了下第一个文件(localfonts.conf):

<?xml version="1.0" encoding="UTF-8"?>

<!--
  DO NOT EDIT THIS FILE! It will be replaced when MiKTeX is updated.
  Instead, edit the configuration file localfonts2.conf.
-->

<fontconfig>
<include>localfonts2.conf</include>
<dir>/Library/Fonts/</dir>
<dir>/System/Library/Fonts/</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/type1</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/opentype</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/truetype</dir>
</fontconfig>

可以看到,我们可以添加路径,不过建议修改的是 localfonts2.conf。按照类似的格式,修改成:

<?xml version="1.0"?>
<fontconfig>
<dir>/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets</dir>
<!-- REMOVE THIS LINE
<dir>Your font directory here</dir>
<dir>Your font directory here</dir>
<dir>Your font directory here</dir>
     REMOVE THIS LINE -->
</fontconfig>

UPDATE: 新版本 macOS 中,路径建议加上 /System/Library/AssetsV2/com_apple_MobileAsset_Font7

<dir>/System/Library/AssetsV2/com_apple_MobileAsset_Font7</dir>

这样,就可以找到 Kaiti SC 了:

$ miktex-fc-list | grep Kaiti
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Regular,標準體,常规体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Regular,標準體,常规体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Bold,粗體,粗体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Bold,粗體,粗体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Black,黑體,黑体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Black,黑體,黑体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: STKaiti:style=Regular,標準體,Ordinær,Normal,Normaali,Regolare,レギュラー,일반체,Regulier,Обычный,常规体

这样就搞定了,用 LaTeX 找字体的时候也没有出现问题了。

如果你用的是 TeX Live,那么直接把上面的 Kaiti.ttc 路径复制到 ~/Library/Fonts 下即可。

COMMON 符号

背景

在编译一个程序的时候,遇到了 undefined symbol 的问题。具体情况是这样的:

  1. 一开始的时候,直接把所有的源代码编译成 .o,再一次性链接,这样不会报错
  2. 后来,把一些代码编译成静态库,即把其中一部分源代码编译成 .o 后,用 ar 合并到一个 .a 中,再和其余的 .o 链接在一起,这时候就报错了:
Undefined symbols for architecture arm64:
  "_abcd", referenced from:
    ...

如果换台机器,编译(使用的是 gcc 10.2.0)就没有问题。

而如果去找这个符号存在的 .o 里,是可以找到的:

$ objdump -t /path/to/abcd.o
0000000000000028         *COM*  00000008 _abcd

在合成的静态库 .a 里,也是存在的(一个定义 + 若干个引用):

$ objdump -t /path/to/libabc.a | grep abcd
0000000000000028         *COM*  00000008 _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd

于是觉得很奇怪,就上网搜了一下,找到了一篇 StackOverflow 讲了这个问题。解决方案很简单,就是:

编译的时候打开 -fno-common 设置

而 gcc 10 不会出错的原因是,它默认从 -fcommon 改成了 -fno-common

COMMON 是什么

这时候,肯定不满足于找到一个解决方案,肯定还是会去找背后的原理。

首先,搜索了一下 COMMON 是什么,找到了 Investigating linking with COMMON symbols in ELF 这篇文章。

文章里讲了 COMMON 是做什么的:

Common symbols are a feature that allow a programmer to 'define' several variables of the same name in different source files. This is in contrast with the more popular way of doing, where you define a variable once in a source file, and reference it everywhere else in other source files, using extern. When common symbols are used, the linker will merge all symbols of the same name into a single memory location, the size of which is the largest type of the individual common symbol definitions. For example, if fileA.c defines an uninitialized 32-bit integer myint, and fileB.c defines an 8-bit char myint, then in the final executable, references to myint from both files will point to the same memory location (common location), and the linker will reserve 32 bits for that location.

文章里还讲了具体的实现方法:一个没有初始化的全局变量,在 -fcommon 的情况下,会设为 COMMON;如果有初始化,就按照初始化的值预分配到 .bss 或者 .data。链接的时候,如果有多个同名的 symbol,会有一个规则决定最后的 symbol 放到哪里;如果有冲突的话,就是我们熟悉的 multiple definition 错误了。

为啥会有这种需求,多个 variable 同名,不会冲突而且共享内存?又在别的地方看到说法,COMMON 是给 ancient 代码使用的,还有的提到了 FORTRAN。于是去搜了一下,果然,FORTRAN 是问题的关键

FORTRAN 里面的 COMMON

用关键词很容易可以搜索到讲 COMMON BLOCK in FORTRAN 的文章,FORTRAN 里面的 COMMON 是一种通过全局存储隐式传递参数的方法。拿文章里的例子:

      PROGRAM MAIN
      INTEGER A
      REAL    F,R,X,Y
      COMMON  R,A,F
      A = -14
      R = 99.9
      F = 0.2
      CALL SUB(X,Y)
      END

      SUBROUTINE SUB(P,Q)
      INTEGER I
      REAL    A,B,P,Q
      COMMON  A,I,B
      END

在函数 MAIN 和 SUB 中,都有 COMMON 语句,而 COMMON 后面的变量,就是存储在一个 COMMON 的 symbol 之中,按照顺序映射到 symbol 的内存地址。尝试编译一下上面的代码,然后看一下 symbol:

$ gfortran -g -c test.f -o test.o
$ objdump -t test.o

test.o: file format Mach-O arm64

SYMBOL TABLE:
0000000000000078 g     F __TEXT,__text _main
0000000000000000 g     F __TEXT,__text _sub_
000000000000000c         *COM*  00000010 ___BLNK__

可以看到,出现了一个叫做 ___BLNK__ 的 COMMON symbol,大小是 16 字节。看一下代码中是如何引用的:

$ objdump -S --reloc test.o

test.o: file format Mach-O arm64

Disassembly of section __TEXT,__text:

0000000000000018 _MAIN__:
;         PROGRAM MAIN
      18: fd 7b be a9                   stp x29, x30, [sp, #-32]!
      1c: fd 03 00 91                   mov x29, sp
;         A = -14
      20: 00 00 00 90                   adrp    x0, #0
        0000000000000020:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      24: 00 00 40 f9                   ldr x0, [x0]
        0000000000000024:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      28: a1 01 80 12                   mov w1, #-14
      2c: 01 04 00 b9                   str w1, [x0, #4]
;         R = 99.9
      30: 00 00 00 90                   adrp    x0, #0
        0000000000000030:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      34: 00 00 40 f9                   ldr x0, [x0]
        0000000000000034:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      38: a1 99 99 52                   mov w1, #52429
      3c: e1 58 a8 72                   movk    w1, #17095, lsl #16
      40: 20 00 27 1e                   fmov    s0, w1
      44: 00 00 00 bd                   str s0, [x0]
;         F = 0.2
      48: 00 00 00 90                   adrp    x0, #0
        0000000000000048:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      4c: 00 00 40 f9                   ldr x0, [x0]
        000000000000004c:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      50: a1 99 99 52                   mov w1, #52429
      54: 81 c9 a7 72                   movk    w1, #15948, lsl #16
      58: 20 00 27 1e                   fmov    s0, w1
      5c: 00 08 00 bd                   str s0, [x0, #8]
;         CALL SUB(X,Y)
      60: e1 63 00 91                   add x1, sp, #24
      64: e0 73 00 91                   add x0, sp, #28
      68: 00 00 00 94                   bl  #0 <_MAIN__+0x50>
        0000000000000068:  ARM64_RELOC_BRANCH26 _sub_
;         END
      6c: 1f 20 03 d5                   nop
      70: fd 7b c2 a8                   ldp x29, x30, [sp], #32
      74: c0 03 5f d6                   ret

可以看到,在 MAIN 中引用 A 的时候,取的地址是 ___BLNK__+4R___BLNK__+0F___BLNK__+8。这和代码里的顺序也是一致的。所以在 SUB 中读 A I B 的时候,对应了 MAIN 中的 A R F。通过这种方式,可以在 MAIN 函数里面隐式地给所有函数传递参数。

此外,COMMON 还可以命名,这样就可以区分不同的参数用途:

        PROGRAM MAIN
        INTEGER A
        REAL    F,R,X,Y
        COMMON  R,A,F
        COMMON /test/ X,Y
        A = -14
        R = 99.9
        F = 0.2
        CALL SUB(X,Y)
        END

        SUBROUTINE SUB(P,Q)
        INTEGER I
        REAL    A,B,P,Q
        COMMON  A,I,B
        END

代码添加了一行 COMMON /test/,观察一下 symbol:

$ objdump -t test.o

test.o: file format Mach-O arm64

SYMBOL TABLE:
0000000000000088 g     F __TEXT,__text _main
0000000000000000 g     F __TEXT,__text _sub_
000000000000000c         *COM*  00000010 ___BLNK__
0000000000000008         *COM*  00000010 _test_

和预期的一致:出现了新的 COMMON symbol,对应了 named COMMON Block 里面的变量 X 和 Y。

再看一下汇编里怎么引用的:

;         CALL SUB(X,Y)
      60: 00 00 00 90                   adrp    x0, #0
                0000000000000060:  ARM64_RELOC_GOT_LOAD_PAGE21  _test_
      64: 00 00 40 f9                   ldr     x0, [x0]
                0000000000000064:  ARM64_RELOC_GOT_LOAD_PAGEOFF12       _test_
      68: 01 10 00 91                   add     x1, x0, #4
      6c: 00 00 00 90                   adrp    x0, #0
                000000000000006c:  ARM64_RELOC_GOT_LOAD_PAGE21  _test_
      70: 00 00 40 f9                   ldr     x0, [x0]
                0000000000000070:  ARM64_RELOC_GOT_LOAD_PAGEOFF12       _test_
      74: 00 00 00 94                   bl      #0 <_MAIN__+0x5c>
                0000000000000074:  ARM64_RELOC_BRANCH26 _sub_

可以看到,第一个参数(x0)为 _test_,第二个参数(x1)为 _test_+4,和预期也是一样的。

读到这里,就可以理解为啥有 COMMON symbol 了。可能是为了让 C 代码和 FORTRAN 代码可以互操作 COMMON symbol,就有了这么一出。也可能有的 C 库确实用了类似的方法来实现某些功能。

解决方案

但是,这种用法在现在来看是不推荐的,建议还是该 extern 就 extern,另外,在编译静态库的时候,记得加上 -fno-common

Skid Buffer

Skid buffer

Skid buffer 指的就是,对于 valid + ready 的握手信号,用空间(更多的逻辑)来换取时间(更好的时序)的一个硬件模块。

简单来说,背景就是,为了解决 valid 和 ready 信号在数据流水线上一路经过组合逻辑导致的时序问题,在中途加上一些寄存器来阻隔。当然了,代价就是延迟和面积,不过吞吐量还是需要保持的。

由于需求的不同,Skid buffer 也有不同的实现。目前,找到了四个实现,实现上有所不同,特性也不大一样。

统一约定

由于我在 SpinalHDL 语言中重新实现了下面的这些 Skid buffer,所以按照 SpinalHDL 的 Stream 定义接口:

class SkidBufferCommon[T <: Data](
    gen: => T
) extends Component {
  val io = new Bundle {
    val s = slave(Stream(gen))
    val m = master(Stream(gen))
  }
}

在这里,io.s 表示从上游取的数据,io.m 表示传递给下游的数据。

输出信号共有:io.s.readyio.m.validio.m.payload

ZipCPU 版本

第一个版本来自 ZipCPU:

博客地址:Building a Skid Buffer for AXI processing 代码地址:skidbuffer.v

它有两个参数,一个表示是否有额外的输出寄存器(outputReg),一个表示是否低功耗(lowPower)。

FPGACPU 版本

第二个版本来自 FPGACPU:

文章地址:Pipeline Skid Buffer

SpinalHDL S2M 版本

第三个版本来自 SpinalHDL Library 的 s2mPipe:

代码地址:Stream.scala L348

SpinalHDL M2S 版本

第四个版本来自 SpinalHDL Library 的 m2sPipe:

代码地址:Stream.scala L327

四个版本的对比

在研究了代码以后,可以看到这四个版本的区别:

版本 ZipCPU w/ outputReg ZipCPU w/o outputReg FPGACPU S2M M2S
io.s.ready Reg Reg Reg Reg Comb
io.m.valid Reg Comb Reg Comb Reg
io.m.payload Reg Comb Reg Comb Reg
latency 1 0 1 0 1
buffer 数量 1 1 2 1 1

注:

  1. Reg 表示从寄存器输出,Comb 表示从组合逻辑输出
  2. Latency 表示从 io.s.fireio.m.fire 的延迟
  3. Buffer 表示缓冲的 payload 个数
  4. ZipCPU w/o outputReg 和 S2M 实现的逻辑是一样的

形式化验证

为了确认上面这些类型的 Skid Buffer 都可以正常工作,按照 ZipCPU Skid Buffer 的文章,也照着写了几个 property:

1: 在 valid && ~ready 的时候,valid 需要继续保持为高,并且 payload 不变:

// When valid goes high, data is stable and valid stays high before ready
when(past(stream.valid && ~stream.ready && ~outerReset)) {
    slaveAssume(stream.valid);
    if (dataStable) {
        slaveAssume(stable(stream.payload.asBits));
    }
}

2: 在 reset 释放的第一个周期里,valid 不能为高:

参考 AXI 标准 (IHI0022E Page 38 A3.1.2) 原文:

The earliest point after reset that a master is permitted to begin driving ARVALID, AWVALID, or WVALID HIGH is at a rising ACLK edge after ARESETn is HIGH.
// Valid is low in the first cycle after reset falls
when(pastValid && past(outerReset) && ~outerReset) {
    slaveAssume(~stream.valid);
}

3: 添加 cover property,要求 io.sio.m 可以连续若干个周期 valid && ready,保证吞吐率:

cover(
    pastValid && genPast(pastValid, null, cycles) && genPast(
        ~outerReset,
        null,
        cycles
    ) && genPast(stream.fire, payload, cycles)
)

采用 yosys-smtbmc 工具验证了以上四种 Skid buffer 都满足这些属性。

在 M1 上用 QEMU 运行 Debian 虚拟机

背景

看到 @jsteward 在 M1 的 QEMU 中运行了 Windows on ARM,所以我先来试试 Debian on AArch64,这样会简单一些。

参考:https://gist.github.com/niw/e4313b9c14e968764a52375da41b4278#file-readme-md

大约需要 3G 的硬盘空间。

安装 QEMU w/ M1 patches

目前上游的 QEMU 还不支持 M1 的 Hypervisor framework,需要打 patch:

git clone https://mirrors.tuna.tsinghua.edu.cn/git/qemu.git
cd qemu
git checkout master -b wip/hvf
curl 'https://patchwork.kernel.org/series/400619/mbox/'|git am --3way
mkdir build
cd build
../configure --target-list=aarch64-softmmu --enable-cocoa --disable-gnutls
make -j4

编译后,得到 qemu-system-aarch64 的二进制

准备好文件系统

需要下载 EFI 固件Debian 安装镜像,解压前者以后把文件放同一个目录中,并且创建需要的文件:

$ ls *.fd
QEMU_EFI.fd   QEMU_VARS.fd
$ dd if=/dev/zero of=pflash0.img bs=1m count=64
$ dd if=/dev/zero of=pflash1.img bs=1m count=64
$ dd if=QEMU_EFI.fd of=pflash0.img conv=notrunc
$ dd if=QEMU_VARS.fd of=pflash1.img conv=notrunc
$ $QEMU/qemu-img create -f qcow2 disk.qcow2 40G

安装 Debian 系统

接着,执行以下的命令,然后按照提示安装系统:

$ $QEMU/qemu-system-aarch64 \
  -serial mon:stdio \
  -M virt,highmem=off \
  -accel hvf \
  -cpu cortex-a72 \
  -smp 4 \
  -m 4096 \
  -drive file=./pflash0.img,format=raw,if=pflash,readonly=on \
  -drive file=./pflash1.img,format=raw,if=pflash \
  -device virtio-scsi-pci \
  -device virtio-gpu-pci \
  -device qemu-xhci \
  -device usb-kbd \
  -device usb-tablet \
  -drive file=./disk.qcow2,if=none,id=boot,cache=writethrough \
  -device nvme,drive=boot,serial=boot \
  -drive if=none,id=cd,file=debian-10.7.0-arm64-xfce-CD-1.iso,media=cdrom \
  -device scsi-cd,drive=cd \
  -display default,show-cursor=on

需要注意的是,如果用 -cdrom 选项,Debian 会无法识别,所以需要走 SCSI。安装完成后,第一次重启可能会显示失败,不用管。另外,安装界面只在串口处显示,但不会显示在 GUI 中,估计是因为 BUG(感谢 @Harry-Chen 指出)。

启动系统

安装好后,运行下面的命令来启动 Debian 系统:

$ $QEMU/qemu-system-aarch64 \
  -monitor stdio \
  -M virt,highmem=off \
  -accel hvf \
  -cpu cortex-a72 \
  -smp 4 \
  -m 4096 \
  -drive file=./pflash0.img,format=raw,if=pflash,readonly=on \
  -drive file=./pflash1.img,format=raw,if=pflash \
  -device virtio-gpu-pci \
  -device virtio-scsi-pci \
  -device qemu-xhci \
  -device usb-kbd \
  -device usb-tablet \
  -drive file=./disk.qcow2,if=none,id=boot,cache=writethrough \
  -device nvme,drive=boot,serial=boot \
  -display default,show-cursor=on \
  -nic user,model=virtio

注意参数和上面有所不同。启动后就可以在 GUI 上看到 Debian 登录的界面了。

网络

起来以后,可以看到一个网卡 enp0s1 启动并获取 IP 地址:

$ ip l set enp0s1 up
$ dhclient enp0s1

获取到一个 IP 地址后,就可以上网了。

已知问题

在虚拟机内重启以后,可能会启动失败。退出 QEMU 进程重新启动即可。