2020년 9월 12일 토요일

ESPRESSObin Ultra 보드와 MACCHIATObin 보드의 Switch & Ethernet Device Driver 분석

이번 blog post 부터는 앞으로 몇차례에 걸쳐 Marvell chip을 사용하는 ESPRESSObin Ultra 보드MACCHIATObin 보드에 관한 이야기(네트워크 관련)를 해 보고자 한다. 😎  이번에 소개하는 보드는 본 blog의 취지(?)하고는 맞지 않게 좀 비싼 보드들이다. 🏂




목차
1. Docker 환경 설정
2. ESPRESSObin Ultra 보드 소개
3. ESPRESSObin Ultra 보드의 Switching Device Driver 분석
4. MACCHIATObin 보드 소개
5. MACCHIATObin 보드의 Ethernet Device Driver 분석
6. 응용편:  MACCHIATObin 보드를 SoftEther VPN Gateway로 만들기
7. References


1. Docker 환경 설정 🐋
본론에 들어가기에 앞서서, 이번 장에서는 docker를 기반으로 개발 환경을 꾸미는 과정을 먼저 소개해 보고자 한다. Docker는 개발 환경을 맞추기 어려운 경우(예: PC Ubuntu 18.04 64bit <=> 실제로 필요한 환경 Ubuntu 12.04 32bit)에 이를 효과적으로 해결하는데 도움을 준다. 물론 VMware나 VirtuaBox와 같은 가상 환경을 사용해도 좋지만, 성능 면에서 볼 때 docker가 보다 효과적이라 볼 수 있다. Build하는데만 반나절 이상 걸리는 project(예: Android, Yocto ...)를 상상해 보라 ... VirtualBox로 build 하던 도중 resource 부족 문제로 중단되어 버린 경험이 있는 개발자라면, docker가 대안이라는 것을 쉽게 알 수 있을 것이다.

[그림 1.1] Docker 아키텍쳐
[출처: https://www.iconspng.com/image/48298/docker-architecture]

Docker를 활용하는 방법에 관해서는 이미 (3년 전에) 한 차례 소개한 바가 있다.


1.1) Docker 설치
이 절에서 소개하는 내용은 (기본적으로) 아래 site 내용을 참고하였다.


          => docker 명령어 사용법을 아주 간결하게 설명해 주고 있다.  👍💯


<Desktop PC - ubuntu 18.04>
$ sudo apt update
  =>  먼저, package database를 update해 준다.

$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
  => apt가 HTTPS를 사용하여 package를 설치하기 위해 필요한 몇가지 패키지를 설치한다.

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  => 공식 docker repository용 GPG key를 추가한다.

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
  => APT source에 docker repository를 추가한다.

$ sudo apt update
  => docker repository가 추가되었으니, package database를 다시 한번 update한다.

$ apt-cache policy docker-ce
  => 설치할 docker 후보를 확인해 본다(이 과정은 반드시 필요한 과정은 아님).

$ sudo apt install docker-ce
  => 실제로 docker ce(community edition)를 설치한다.

$ sudo systemctl status docker
  => 설치 후, 아래와 같은 내용이 출력된다면 정상적으로 설치가 된 것이다.

● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-09-09 10:52:56 KST; 17s ago
     Docs: https://docs.docker.com
 Main PID: 22955 (dockerd)
    Tasks: 11
   CGroup: /system.slice/docker.service
           └─22955 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.135659400+09:00" level=warning msg="Your k
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.135663932+09:00" level=warning msg="Your k
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.135668335+09:00" level=warning msg="Your k
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.135766281+09:00" level=info msg="Loading c
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.352215690+09:00" level=info msg="Default b
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.387724446+09:00" level=info msg="Loading c
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.430656835+09:00" level=info msg="Docker da
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.430851347+09:00" level=info msg="Daemon ha
 9월 09 10:52:56 mars dockerd[22955]: time="2020-09-09T10:52:56.444891890+09:00" level=info msg="API liste
 9월 09 10:52:56 mars systemd[1]: Started Docker Application Container Engine.

$ ps aux|grep docker
  => docker를 설치하면 실제로 dockerd라는 daemon이 하나 구동되게 된다.
root     22955  0.3  1.0 976400 82544 ?        Ssl  10:52   0:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
chyi     23498  0.0  0.0  15720  1068 pts/1    S+   10:53   0:00 grep --color=auto docker

$ sudo usermod -aG docker chyi
  => sudo 없이 docker 명령을 바로 사용할 수 있도록 자신의 계정을 docker group에 추가해 주자.
$ su - chyi
  => 계정 전환을 하고 난 후에는 sudo 없이 docker 명령 실행 가능해짐.
  => 혹은 logout 후 다시 login하게 되면 sudo 없이 docker 명령 실행 가능해짐.

chyi@mars:~$ docker info
Client:
 Debug Mode: false

Server:
 Containers: 2
  Running: 0
  Paused: 0
  Stopped: 2
 Images: 1
 Server Version: 19.03.12
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
 init version: fec3683
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 5.4.0-45-generic
 Operating System: Ubuntu 18.04.2 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 7.673GiB
 Name: mars
 ID: E4YZ:D7CM:RT6Y:U673:ACHU:7C6C:2QTK:UVKV:ZZS2:KOUD:5EVW:LUZR
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support

1.2) Docker 상에 Ubuntu 18.04 설치
Docker가 설치되었으니, 이제부터는 실제 개발 환경(Ubuntu 18.04와 Ubuntu 16.04)를 꾸며 보도록 하겠다. 이 절에서는 우선 (ESPRESSObin Ultra 보드를 위해) Ubuntu 18.04 환경을 먼저 만들어 보도록 하겠다(MACCHIATOBin 보드를 위한 Ubuntu 16.04는 4장에서 설치한다).

$ cd ~/workspace
$ vi Dockerfile_ubuntu1804
  => 파일을 하나 열고 아래와 같은 내용을 입력하자.

[그림 1.2] Dockerfile_ubuntu1804 파일 내용

📌 ubuntu, centos, redis 등 OS나 program의 이름을 가진 이미지가 공식 이미지이다. 한편 ubuntu:18.04 처럼 뒤에 tag를 붙여 주면 해당 버젼을 내려 받게 된다. tag 자리에 latest를 적어 주면 최신 버젼을 받게 된다. 또한 32bit 버젼을 download 받고 싶다면 i386/ubuntu:12.04 이런 식으로 명시해 주면 된다.
📌 Dockerfile 대신 "docker pull ubuntu:18.04" 명령 수행 후, docker run 하여 필요한 package를 설치해도 된다.
📌 Dockerfile 작성 방법과 관련해서는 아래 site를 참조하기 바란다.

$ docker build -f Dockerfile_ubuntu1804 -t u1804 .
  => 앞서 작성한 Dockerfile을 토대로 docker image를 생성하도록 하자. 마지막의 '.' 을 빼먹으면 안된다.

[그림 1.3] Dockerfile_ubuntu1804 파일을 이용하여 docker image 생성 모습

$ docker images
  => 생성된 docker image를 출력해 보면 다음과 같다. 아래 내용 중 u1804가 새로 생성된 docker image이다.
  => ubuntu 18.04가 ubuntu 18.04 original image이다. 참고로, 아래 내용 중에는 이전에 설치한 몇가지 이미지가 함께 출력되고 있다.

[그림 1.4] 생성된 docker image

$ docker run -i -t --name esbin1804 u1804 /bin/bash
  => 새로 생성한 docker image를 실행(container라고 함)해 보도록 하자.

root@26de74ece3fd:/# uname -a
Linux 26de74ece3fd 5.4.0-45-generic #49~18.04.2-Ubuntu SMP Wed Aug 26 16:29:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

(Dockerfile에 누락된) 몇가지 package를 추가로 설치해 보도록 하자.

root@26de74ece3fd:/# apt-get install ssh
  => docker ubuntu 18.04에 ssh server를 설치한다.

root@26de74ece3fd:/# apt-get install vim
  => vim도 설치하자.

root@26de74ece3fd:/# vi /etc/ssh/sshd_config
  => ssh root login을 위해 아래 내용을 추가하자.
...
#PermitRootLogin prohibit-password
PermitRootLogin yes
...

root@26de74ece3fd:/# service ssh restart
  => ssh server를 재구동시키자.

root@26de74ece3fd:/# apt-get install net-tools
  => ifconfig 등의 명령을 위해 net-tools를  설치한다.

[그림 1.5] ifconfig 명령 실행 모습

root@26de74ece3fd:/# passwd
  => root password를 설정하자.
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully

_____________________
chyi@mars:~$ ssh root@172.17.0.2
  => 다른 terminal 창에서 ssh로 현재 동작 중인 docker container에 로긴해 본다.

[그림 1.6] ssh로 docker container에 로긴한 모습

_____________________
다시 docker container 창으로 돌아와서 ...

root@26de74ece3fd:/# adduser chyi
  => 사용자를 하나 추가하자.
Adding user `chyi' ...
Adding new group `chyi' (1000) ...
Adding new user `chyi' (1000) with group `chyi' ...
Creating home directory `/home/chyi' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for chyi
Enter the new value, or press ENTER for the default
Full Name []: 
Room Number []: 
Work Phone []: 
Home Phone []: 
Other []: 
Is the information correct? [Y/n] y

📌 사용자 계정 추가 등의 과정도 Dockerfile에 넣어주면 편리하다.

root@26de74ece3fd:/# exit

마지막으로 docker container를 빠져 나온 후, docker container 상에서 변경한 내용(몇가지 패키지 추가 및 파일 수정)을 docker image에 저장하는 작업(commit)을 진행하도록 하자. 참고로, 이 과정을 빼먹을 경우,  docker run 명령 재 수행 시, 이전에 수정했던 내용이 모두 사라져 버리게 되니, 반드시 이 과정을 해 주어야 한다.

$ docker commit -a "chunghan <chunghan.yi@gmail.com>" -m "add some packages" esbin1804 u1804
sha256:026516e59f658743596643446bdb86a7312a0957755586d0eab8c25685e5019f

  ➔ 사용방법 : docker commit < 옵션 > < 컨테이너 이름 > < 이미지 이름 >:< 태그 >
  ➔ < 컨테이너 이름 >: 직전에 docker run -i -t --name esbin1804 u1804 /bin/bash 에서
지정한 name
  ➔ < 이미지 이름 >:< 태그 >   (참고: : <태그>는 생략 가능함)

$ docker rm esbin1804
  => (다시 run 하기 전에)이전 container의 이름을 지워주도록 하자. 만일 이 과정을 빼먹을 경우, 매번 새로운 이름을 작명(?)해야 하는 수고를 해 주어야 한다.
  => docker ps -a 명령으로 이전에 실행했거나 현재 동작 중인 docker container를 확인할 수 있다.

$ docker run -i -t --name esbin1804 u1804 /bin/bash
root@a62c173f2d74:/#
  => commit하기 전에 수행했던 내용이 그대로 보존되어 있으면 정상적으로 commit된 것이다.

[그림 1.7] run_u1804.sh script 예

1.3) Docker 관련 몇가지 Tips
Q1) Host OS와 docker container간에 파일을 복사하고자 한다면 ?
A1-1) docker cp 명령을 사용합니다.
    $ docker cp esbin1804:/home/chyi/test/bbb.txt .

A1-2) 공유 디렉토리를 이용합니다. docker run시 아래와 같이 -v <host OS 디렉토리>:<container 디렉토리>를 주게 되면, 두 디렉토리간에 파일이 공유되게 됩니다.
   $ docker run -i -t -v /var/lib/tftpboot:/data --name esbin1804 u1804 /bin/bash

Q2) Container에서 tcp 80번 port를 열고, host OS의 80번 port와 연결하려면 ?
A2) -p 80:80 option을 주어 실행하면 됩니다. http://<Host IP>:80 하면 container에 접속할 수 있습니다.
     $ docker run -i -t -p 80:80 -v /var/lib/tftpboot:/data --name esbin1804 u1804 /bin/bash

Q3) 현재 동작 중인 container와 현재까지 실행되고 있거나, 실행된 적이 있는 모든 container 목록을 보려면 ? 혹은 이들을 제거하려면 ?
A3) 
     $ docker ps              # 현재 동작 중인 container 출력
     $ docker ps -a         #  현재까지 실행되고 있거나, 실행된 적인 있는 모든 container 목록 출력

     $ ​ docker rm $(docker ps -a -q)          # 모든 container 정보 삭제
     $ ​ docker rmi $(docker images -q)    # 모든 image 제거
_________________________________________

이상으로 본격적인 내용 소개에 앞서서 docker를 활용한 개발 환경 구축 방법을 소개해 보았다. Docker ! 정말로 훌륭한 녀석(?)이다. 잘 활용하면 개발에 커다란 도움을 줄 것으로 확신한다.


2. ESPRESSObin Ultra보드 소개
ESPRESSObin 보드와 관련해서는 이전 blog post를 통해 한 차례 소개한 바가 있다. 오늘은 최근(2019년) 새롭게 등장한 ESPRESSObin Ultra 보드(이하 Ultra 보드로 칭하겠음)를 소개해 보고자 한다. 사실 7월말에 하나를 주문했는데, (9월인데) 아직도 도착을 안하고 있다 😠

2.1) Ultra 보드 소개

[그림 2.1] ESPRESSObin Ultra 보드 - Access Point style 구성 [출처: 참고문헌 2]


[그림 2.2] ESPRESSObin Ultra 보드 H/W Block도 [출처: 참고문헌 2]

📌 H/W Block도 우측 하단을 보면, Marvell 88E6341 switching chip이 RGMII interface를 통해 CPU에 연결되어 있음을 알 수 있다.


[그림 2.3] ESPRESSObin Ultra 보드 주요 스펙

2.2) Ultra 보드 개발 환경 설정
그럼, 먼저 Ultra board용 bootloader, kernel source를 내려 받아 build를 해 보도록 하겠다. 이 절에서 소개하는 내용은 아래 site의 내용을 기준으로 한 것이다. Home page에 정식으로 내용이 올라와 있지 않은 것으로 보아, 아직도 개발 중이던지 ... 뭔가 약간의 문제가 있는 듯 보인다.


docker run -i -t --name esbin1804 u1804 /bin/bash
root@d9419ee9c06e:/# su - chyi
  => 이후의 모든 작업은 root 말고 사용자 계정으로 진행해도록 하자.  

chyi@d9419ee9c06e:~$ cd ~; mkdir workspace; cd workspace

<repo로 source download하기>
chyi@d9419ee9c06e:~/workspace$ git config --global user.email "chunghan.yi@gmail.com"
chyi@d9419ee9c06e:~/workspace$ git config --global user.name "Chunghan Yi"
  => git을 사용하려면 반드시 이 과정을 거쳐야 한다. 이는 기본 중의 기본^^

chyi@d9419ee9c06e:~/workspace$ repo init -u https://github.com/globalscaletechnologies/manifest.git -b espressobin-ultra -m espressobin_ultra-1.0.0.xml

chyi@d9419ee9c06e:~/workspace$ repo sync
  => repo sync를 해야 비로소 source code download가 시작된다.

chyi@d9419ee9c06e:~/workspace$ ls -al

[그림 2.4] ESPRESSObin Ultra source code 목록

<arm64 linaro toolchain 설치하기>
chyi@d9419ee9c06e:~/workspace$ mkdir toolchain;cd toolchain/
chyi@d9419ee9c06e:~/workspace/toolchain$ wget https://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz
  => linaro toolchain을 내려 받는다.

chyi@d9419ee9c06e:~/workspace/toolchain$ tar -xvf ./gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz
  => 압축을 푼다.

<bootloader용 arm32 toolchain 설치하기>
root@d9419ee9c06e:~# apt-get install gcc-arm-linux-gnueabi
  => 근데, 이미 설치가 되어 있다.
Reading package lists... Done
Building dependency tree       
Reading state information... Done
gcc-arm-linux-gnueabi is already the newest version (4:7.4.0-1ubuntu2.3).
0 upgraded, 0 newly installed, 0 to remove and 5 not upgraded.

📌 얘가 진짜 필요한게 맞는지 잘 모르겠다. 이후 진행되는 것을 보면 u-boot이 arm64용으로 build가 되는 듯한데 ... 이 부분은 추후 재 확인을 해 보도록 하겠다.

<Compile 환경 설정하기>
$ vi ~/.bashrc
  => bashrc에 아래의 환경 설정 내용을 추가해 주자.
...
export PATH=/home/chyi/workspace/toolchain/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin:$PATH

# Set the cross compiler
export CROSS_COMPILE=/home/chyi/workspace/toolchain/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-

# Set path for MV_DDR component
export MV_DDR_PATH=/home/chyi/workspace/mv_ddr_marvell

# Set U-Boot image path
export BL33=/home/chyi/workspace/u-boot-marvell/u-boot.bin

# Set WTP Tools path
export WTP=/home/chyi/workspace/a3700-utils-marvell
~

$ source ~/.bashrc
  => 위의서 설정한 환경 변수를 적용해 준다.

u-boot, kernel 등을 compile하기 전에 몇가지 package를 더 설치하자. 사실 아래 내용은 (build 과정에서 발견하여) 나중에 추가한 것이다.

root@d9419ee9c06e:~# apt-get install -y libncurses-dev
root@d9419ee9c06e:~# apt-get install -y bc


<u-boot build하기>
chyi@d9419ee9c06e:~/workspace$ make -C ./u-boot-marvell gti_ccpe-88f3720_defconfig
chyi@d9419ee9c06e:~/workspace$ make -C ./u-boot-marvell menuconfig
chyi@d9419ee9c06e:~/workspace$ make -C ./u-boot-marvell DEVICE_TREE=armada-3720-ccpe

<ATF build하기>
chyi@d9419ee9c06e:~/workspace$ make -C ./atf-marvell DEBUG=0 USE_COHERENT_MEM=0 LOG_LEVEL=20 CLOCKSPRESET=CPU_1000_DDR_800 PLAT=a3700 DDR_TOPOLOGY=5 all fip
📌 ATF는 ARM Trusted Firmware를 뜻한다.

<kernel build하기>
chyi@d9419ee9c06e:~/workspace$ make -C ./linux ARCH=arm64 gti_ccpe-88f3720_defconfig
chyi@d9419ee9c06e:~/workspace$ make -C ./linux ARCH=arm64 menuconfig
chyi@d9419ee9c06e:~/workspace$ make -C ./linux ARCH=arm64 -j4

<통합 이미지 생성하기>
오 ! 이런~ 전체 build(환경 설정 포함)를 해 주는 script가 이미 준비되어 있다. 얘를 이용하여 다시 build해 보자.

chyi@d9419ee9c06e:~/workspace$ source ./env-espressobin_ultra.sh
chyi@d9419ee9c06e:~/workspace$ gtibuild bootloader
chyi@d9419ee9c06e:~/workspace$ gtibuild kernel
chyi@d9419ee9c06e:~/workspace$ gtibuild all

[그림 2.5] ESPRESSObin Ultra build 결과물


<Target board에 설치하기>
<TBD> 
지금까지 build한 내용은 보드가 도착하면 시도해 보도록 하자. 참고로, (앞선 build 과정에서 보이지 않았던) rootfs는 prebuilt 형태로 제공된다.


__________________________
root@d9419ee9c06e:/# exit
$ docker commit -a "chunghan <chunghan.yi@gmail.com>" -m "add some packages" esbin1804 u1804
  => 지끔까지 작업한 내용을 docker image에 반드시 저장하자.



3. ESPRESSObin Ultra보드의 Switching Device Driver 분석
이 장의 내용은 [참고문헌 3]에서 영감을 받아 작성하였다.


3.1) Ultra 보드 Switch device 소개
ESPRESSObin Ultra 보드는 아래 그림과 같이 Marvell 88E6341 switching chip(Topaz Switch라고도 함)을 사용한다. 이 88E6341 switch는 왼쪽으로는 CPU랑 RGMII(data 통신용) 및 MDC/MDIO(제어용)로 연결되어 있으며, 오른쪽으로는 4개의 LAN port와 1개의 WAN port(얘는 다시 SGMII interface를 통해 88E1512 Gb PHY와 연결)와 연결되어 있다.


[그림 3.1] Marvell 88E6341 Ethernet Switch(1) [출처: 참고문헌 2]

아래 그림은 Marvell 88E6341 Ethernet Switch의 구성 요소를 간략히 보여주고 있다.

[그림 3.2] Marvell 88E6341 Ethernet Switch(2) [출처: 참고문헌 6]

한편, Linux kernel에는 위와 같은 switch device를 위한 framework(switchdev)이 이미 정의되어 있는데, 이와 관련해서는 아래 내용을 살펴볼 필요가 있다.

     1) Documentation/networking/switchdev.txt
     2) net/switchdev/switchdev.c
     3) include/net/switchdev.h

[그림 3.3] linux kernel switch device driver model

대개의 경우, CPU(정확하게는 MAC)와 switching chip은 PCIe 혹은 xMII 등의 interface를 통해 data를 주고 받으며, MDC/MDIO(= SMI) or SPI 등을 통해 control이 이루어지게 된다.

[그림 3.4] Ethernet Switch와 CPU의 관계도(개념도) [출처: 참고문헌 3]

그런데, Marvell은 위의 switchdev framework으로는 부족하다고 판단했는지, 자신들의 switch chip을 위해 DSA(Distributed Switch Architecture)라는 새로운 switch framework을 별도로 설계하였다. 이를 그림으로 그려보면 다음과 같은데, 이는 여러 개의 switching chip을 CPU에 붙이는 경우(cascade 구성)를 고려한 것으로, 처음에는 Marvell에서 만들었으나, 이후 다른 vendor들에서도 점차로 이 framework을 사용하는 추세인 듯 하다.

[그림 3.5] DSA(Distributed Switch Architecture) 개념도 [출처: 참고문헌 3]

3.2) Switch device driver 분석
이 절에서는 Marvell 88E6341 switching chip을 위한 device tree 및 driver(DSA driver)를 분석해 보도록 하겠다.

시작에 앞서 DSA의 개념을 보다 구체적으로 이해할 필요가 있겠는데, 이를 위해서는 아래 파일을 먼저 살펴 보아야 한다.

     1) Documentation/networking/dsa/dsa.txt
     2) Documentation/devicetree/bindings/net/dsa/dsa.txt
     3) Documentation/devicetree/bindings/net/dsa/marvell.txt
     4) Documentation/devicetree/bindings/net/ethernet.txt

다음으로 살펴볼 내용은 device tree이다.

armada-37xx.dtsi
|
v
armada-372x.dtsi
|
v
armada-3720-ccpe.dts

(긴 말할 것 없이) 이 중 맨 아래에 있는 dts 파일 즉, arch/arm64/boot/dts/marvell/armada-3720-ccpe.dts를 mdio node를 중심으로 분석해 보면 다음과 같다.
______________________________________________________________
&eth0 {     /* switch device를 cpu에 연결시켜 주는 device, 역시 앞서 정의되어 있는 eth0를 override하여 재 정의하고 있음 */
    pinctrl-names = "default";
    pinctrl-0 = <&rgmii_pins>;
    phy-mode = "rgmii-id";           /* switch와 연결되는 interface: RGMII */
    status = "okay";

    fixed-link {
        speed = <1000>;
        full-duplex;
    };
};

&mdio {     /* DSA에서는 이런 구문이 여러개 존재할 수 있음, &mdio는 앞서 정의된 mdio를 override하여 재 정의하는 것을 뜻함.  */
    status = "okay";

    extphy: ethernet-phy@0 {
        reg = <1>;
    };

    switch0: switch0@1 {      /* switching chip을 기술해 주는 부분 */
        compatible = "marvell,mv88e6085";     /* 이걸로 driver 검색 */
        #address-cells = <1>;
        #size-cells = <0>;       
        reg = <3>;

        dsa,member = <0 0>;      /* cluster 0, switch 0을 의미 */

        ports {      /* switch에 붙어 있는 ports를 기술해 주는 부분 */
            #address-cells = <1>;
            #size-cells = <0>;

            switch0port0: port@0 {
                reg = <0>;
                label = "cpu";       /* 0번 port를 cpu port로  사용 */
                ethernet = <&eth0>;      /* cpu port의 경우는 ethernet property를 추가해 줌 */
            };

            switch0port1: port@1 {
                reg = <1>;
                label = "lan0";
                phy-handle = <&switch0phy1>;             /* (A) */
            };

            switch0port2: port@2 {
                reg = <2>;
                label = "lan1";
                phy-handle = <&switch0phy2>;
            };

            switch0port3: port@3 {
                reg = <3>;
                label = "lan2";
                phy-handle = <&switch0phy3>;
            };

            switch0port4: port@4 {
                reg = <4>;
                label = "lan3";
                phy-handle = <&switch0phy4>;
            };

            switch0port5: port@5 {
                reg = <5>;
                label = "wan";
                phy-handle = <&extphy>;
                phy-mode = "sgmii";       /* wan port는 SGMII interface를 통해 외부 phy와 연결되어 있음 */
            };
        };

        mdio {   /* 이름이 앞서와 겹쳐서 헷갈릴 수 있는데, 여기서의 mdio는 mdio-bus를 의미함 - 여기서의 이름은 크게 중요치 않음. */
            #address-cells = <1>;
            #size-cells = <0>;

            switch0phy1: switch0phy1@11 {     /* (A) */
                reg = <0x11>;     /* mdc/mdio로 연결되는 PHY는 i2c 장치 처럼 주소를 갖는다 */
            };
            switch0phy2: switch0phy2@12 {
                reg = <0x12>;
            };
            switch0phy3: switch0phy3@13 {
                reg = <0x13>;
            };
            switch0phy4: switch0phy4@14 {
                reg = <0x14>;
            };
        };
    };
};
______________________________________________________________

이번에 살펴 볼 부분은 device driver 쪽이다. device driver를 분석할 때는 (전체를 이해하는게 어려울 수 있으니) 초기화 코드(init, probe 등)와 사용자와의 interface(sysfs, read/write/ioctl, netlink socket 등)를 중심으로 살펴볼 필요가 있다.

Source 위치는 아래와 같이 grep으로 찾을 수 있다.
$ grep -rl "marvell,mv88e6085" *
linux/drivers/net/dsa/mv88e6xxx/chip.c

DSA driver의 전체 구조를 파악하는 것은 생각 만큼 간단하지가 않다. [참고 문헌 5]는 DSA의 전체 구조는 물론이고, switchdev, swconfig 등 기존에 구현되어 있는 switch 관련 framework을 전반적으로 소개해 주고 있어, 여기에 소개한다.

[그림 3.6]  DSA Architecture(1) [출처: 참고문헌 5]

[그림 3.7]  DSA Architecture(2) [출처: 참고문헌 5]

[그림 3.8]  DSA Switch Tags 처리(1) [출처: 참고문헌 5]

[그림 3.9]  DSA Switch Tags 처리(2) [출처: 참고문헌 5]

이상의 내용을 기초로 하여 코드를 분석해 보아야 하는데, 지면 관계상, 여기에서는 chip.c 파일의 초기화 부분과 probe 함수 내용을 capture하는 것으로 코드 분석을 대신하고자 한다. 👻

[그림 3.10] Marvell mv88e6xxx driver 초기화 코드(1)

[그림 3.11] Marvell mv88e6xxx driver 초기화 코드(2) - probe 함수


<여기서 잠깐 !>
Ultra 보드에는 4MB 크기의 SPI NOR flash가 한개 장착되어 있다. 아래 내용은 이와 관련한 device tree 내용을 capture한 것으로, NOR flash 내에 3개의 파티션 즉, u-boot, hw-info, u-boot-env이 위치하고 있음을 알 수 있다. 이는 값비싼 SPI NOR flash에는 자주 변경하지 않으면서 크기가 작은 u-boot만을 넣어 두고, 용량이 큰 linux kernel과 rootfs는 값싼 eMMC에 배치하는 전형적인 구성이라고 볼 수 있다.

[그림 3.12] SPI NOR flash에 대한 device tree 표현

📌 SPI flash는 SPI interface를 통해 CPU와 연결된다. SPI는 CS(Chip Select), SCK(Serial Clock), MOSI(Master Output Slave Input), MISO(Master Input Slave Output)라는 4가닥 선을 사용하는 serial 통신 방식이다. 요즘은 이 방식을 많이 사용한다.
____________________________

<여기서 잠깐 !>
최근에 출시된 802.11ax(WiFi 6)를 지원하는 보드(SoC: Qualcomm IPQ6010)가 있어 여기에 소개해 본다.


[그림 3.13] WiFi 6(802.11ax)를 지원하는 보드 - DR6018 v2
____________________________


늘 느끼는 거지만 (요즘 한층 더 복잡해진) linux device driver를 분석하는 것은 결코 만만한 일이 아니다. 특히 device 별로 새로운 framework이 소개되고 있는 탓에 이해를 더 어렵게 만들고 있는 것 같다.

시작할 때 의도했던 것과는 다르게 분석 내용이 너무 수준 이하(?)라는게 좀 아쉽다. 부족한 부분은 추후 좀 더 보충해 볼 것을 기약하며 이번 장을 마치도록 하겠다. 😅


To be continued ... 


7. References
[2] ESPRESSObin ULTRA- Quick Start Guide -Rev 03
[3] Ethernet switch support in the Linux kernel, Alexandre Belloni, Bootlin
[4] From the Ethernet MAC to the link partner, Maxime Chevallier, Antoine Ténart, Bootlin
[6] marvell-link-street-88E6341-product-brief.pdf
[7] http://wiki.macchiatobin.net/tiki-index.php?page=BSP+HowTo
[8] https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04
[9] http://pyrasis.com/Docker/Docker-HOWTO#ps


SlowBoot

댓글 없음:

댓글 쓰기