임베디드 시스템 개발 및 분석 중 라즈베리 파이4를 통해 디버깅 및 테스트를 하는 것이 제일 간단하고 유용할 것으로 예상되어 찾아본 결과 라즈베리파이4 자체의 JTAG Pin을 활용하여 ARM64 환경의 Bare-Metal Code 테스트를 진행 할 수 있으며 쉽게 테스트 및 디버깅이 가능하여 테스트 및 응용에 매우 강력하고 유용한 플랫폼으로 활용이 가능할 것으로 생각한다.

 

대략적인 연결 방법은 라즈베리파이4 Bare-Metal Code 로드 및 디버깅과 커널 디버깅에 사용할 수 있도록 OpenOCD를 사용하여 JTAG Interface를 활용 및 연결해야 한다.

그러기 위해서 라즈베리파이의 JTAG Pin을 소유하고 있는 JTAG Debugger (Olimex ARM-USB-OCD-H) 연결하여 TAP 인식 및 ARM Core Debugging 이 가능하다.

 

라즈베리파이4의 JTAG Pin 위치는 하기와 같다.

위에 나와있는 그림처럼 RPI의 JTAG Pin을 참고하여 사용하고자 하는 JTAG(여기서는 Olimex ARM-USB-OCD-H)의 Pin Map을 확인하여 1대1 연결하여 JTAG 통신 환경을 구축한다.

 

라즈베리파이에서 JTAG Pin을 사용하기 위해서는 Default Pin Func(Mux)가 JTAG 용도로 정의 되어 있지 않기 때문에

하기와 같이 booting 시 config.txt에 기입하여 Pin Mux를 진행하여야 한다.

 

[all]
# Disable pull downs
gpio=22-27=np

# Enable jtag pins (i.e. GPIO22-GPIO27)
enable_jtag_gpio=1

 

OpenOCD에서 rpi4 디버깅 연결을 위하여 하기와 같이 rpi4 관련된 config 파일을 생성해야 하는데 하기와 같이 작성하여

rpi4.cfg를 /share/openocd/script/targets 내에 저장해준다.

 

# SPDX-License-Identifier: GPL-2.0-or-later

# The Broadcom BCM2711 used in Raspberry Pi 4
# No documentation was found on Broadcom website

# Partial information is available in raspberry pi website:
# https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/

if { [info exists CHIPNAME] } {
    set  _CHIPNAME $CHIPNAME
} else {
    set  _CHIPNAME bcm2711
}

if { [info exists CHIPCORES] } {
    set _cores $CHIPCORES
} else {
    set _cores 4
}

if { [info exists USE_SMP] } {
    set _USE_SMP $USE_SMP
} else {
    set _USE_SMP 0
}

if { [info exists DAP_TAPID] } {
    set _DAP_TAPID $DAP_TAPID
} else {
    set _DAP_TAPID 0x4ba00477
}

jtag newtap $_CHIPNAME cpu -expected-id $_DAP_TAPID -irlen 4
adapter speed 3000

dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu

# MEM-AP for direct access
target create $_CHIPNAME.ap mem_ap -dap $_CHIPNAME.dap -ap-num 0

# these addresses are obtained from the ROM table via 'dap info 0' command
set _DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000}
set _CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000}

set _smp_command "target smp"

for { set _core 0 } { $_core < $_cores } { incr _core } {
    set _CTINAME $_CHIPNAME.cti$_core
    set _TARGETNAME $_CHIPNAME.cpu$_core

    cti create $_CTINAME -dap $_CHIPNAME.dap -ap-num 0 -baseaddr [lindex $_CTIBASE $_core]
    target create $_TARGETNAME aarch64 -dap $_CHIPNAME.dap -ap-num 0 -dbgbase [lindex $_DBGBASE $_core] -cti $_CTINAME

    set _smp_command "$_smp_command $_TARGETNAME"
}

if {$_USE_SMP} {
    eval $_smp_command
}

# default target is cpu0
targets $_CHIPNAME.cpu0

 

 

OpenOCD 실행을 하기와 같이 JTAG Interface와 RPI4에 관련된 cfg 파일을 -f 매개인자를 통하여 실행한다.

 

.\bin\openocd.exe -f .\share\openocd\scripts\interface\ftdi\olimex-arm-usb-ocd-h.cfg -f .\share\openocd\scripts\target\rpi4.cfg

 

 

위의 명령을 통해 OpenOCD를 터미널 혹은 PowerShell 상에서 실행하여  아래의 그림과 같이 GDB Server 와 Telnet Port가 생성되는 것을 확인 할 수 있다.

 

 

GDB Server가 정상적으로 실행되면 Rpi4의 Kernel 또는 Bare-metal Code를 작성하여 빌드 후 테스트 할 수 있는 환경을 갖추게 됩니다.

 

이 이후의 GDB Server Remote 연결 및 GDB 디버깅은 다음 포스트에서 진행하도록 하겠습니다.

 

 

'System Programming > Arm' 카테고리의 다른 글

Rockchip AP 부팅 흐름 정리  (0) 2020.03.11
ARM Processor 7개의 Mode  (0) 2016.08.29
ARM Processor 개요  (0) 2016.08.29

일반적인 Rockchip AP의 부팅 흐름 및 Rockchip Wiki의 내용을 정리 및 설명한 글입니다.

글 하단에 참조된 Rockchip wiki에서 추가적인 내용 및 영문 내용을 확인 할 수 있으니 참고 바랍니다.

 

Rockchip 부팅 흐름에서 부트로더 단계에서 두개의 스테이지를 가지고 있는데, 첫번째 스테이지는 Rockchip의 miniloader 또는 u-boot의 tpl/spl 단계에서 ddr initialize 및 초기 설정 후 두번째 스테이지에서 kernel 로 점프하기 위한 준비를 위하여 u-boot을 실행한다.

 

  • U-Boot TPL/SPL or rockchip U-Boot, fully source code
  • Rockchp idbLoader which is combinded by Rockchip ddr init bin and miniloader bin from Rockchip rkbin project;

 

일반적인 U-Boot 부팅 스테이지 사용 시 하기의 표를 참고

Rochip Wiki U-Boot Boot Stage table

 

Boot Rom에 Rom Code(idbLoader)를 Write하여 ddr init 및 초기 AP initialize가 필요한 부분을 수행하고 u-boot 코드로 점프하게 된다.

 

일반적인 이미지 업데이트 모드는 u-boot 에서 GPIO Pin 형태로 Boot-pin 또는 switch 인식하여 전환하게 되는데,

만약 BootRom 내에 이미 Code가 Write 되어 Rom 내역을 수행하고 U-Boot로 jump하는 흐름에서 문제 발생되었을 때는 정상적으로 부팅을 하지 못하게 된다.

 

이 상황에서 Mask Rom 모드로 진입이 가능하도록 eMMC 또는 부팅 매체로 jump되지 못하도록 설정하여 Rom 이미지를 다시 Write 또는 설정한다.

 

Rockhip AP의 Boot Flow는 아래의 그림과 같다.

Rochip Wiki U-Boot Boot Flow

 

위의 그림에서 각각 나와 있는 부팅 흐름은 크게 두가지이다.

 

  • Boot Flow 1 : 일반 적인 Rockchip Boot Flow로 Rockchip miniloader 바이너리를 사용
  • Boot Flow 2 : 일반적인 대부분의 AP 부팅 시퀀스로 U-Boot TPL/SPL에서 DDR init 을 진행하고 다음 스테이지 진행

보통의 경우 위의 두 가지 중 Boot Flow 1을 주로 쓰며, 바이너리 형태로 배포되는 miniloader 를 rkbin github에서 받아

Android Tool을 이용하여 프로그램을 넣는다.

 

*rkbin github : GitHub - rockchip-linux/rkbin: Firmware and Tool Binarys

 

GitHub - rockchip-linux/rkbin: Firmware and Tool Binarys

Firmware and Tool Binarys. Contribute to rockchip-linux/rkbin development by creating an account on GitHub.

github.com

 

 

rkbin 내용은 바이너리 형태로만 배포되며 miniloader 실행 시 포함된 ddr_xx.bin 바이너리 등에 의해 장착된 DRAM 의

사이즈와 연결 상태 및 간이로 DQS를 설정하도록 되어 있다.

 

DDR 설정 이후 u-boot.img로 점프 후 커널을 로드하기 위한 u-boot 코드가 실행되고 이후의 시퀀스는 일반적인 임베디드 리눅스 커널과 동일한 방식으로 커널이 로드된다.

 

DDR 설정을 배포된 바이너리에서 설정하게 되므로 커스터마이즈 및 보드 설계 시 지원되는 DRAM의 파트를 확인해야 하며 Pin Layout에 유의 해야 한다.

 

위의 부팅 시퀀스 이미지 중 ARMv8 이상의 프로세서 또는 안드로이드에서의 Trusted Boot 을 지원하기 위하여 별도의 Trust.img 또는 bl31, bl32.elf, tee.bin 가 존재한다.

 

Security Boot(Trusted)는 리눅스에서도 적용이 가능하나 적용이 필요 없다면 U-boot에서 zImage 와 dtb 파일을 로드하여 커널 부팅을 진행하면 된다.

 

다만, Bootloader(U-boot) 빌드 시 u-boot.img 파일이 필요하므로 빌드 시 img 파일이 생성될 수 있도록 한다.

(Rockchip SDK의 경우 'mkimage' 등의 커맨드를 통해 Rockchip 이미지용 img 파일 생성 레시피가 적용되어 있다.)

 

Android Tool 등의 사용 시 유의해야 할 사항은 Rockchip AP RK31xx, RK33xx 등의 경우 Rockchip Partition으로 생성되므로 커널에서 해당 옵션이 포함되어 빌드 되었는지 참고 하여야 한다.

 

Rockchip Partition으로 생성되어도 파일시스템 형식은 ext3,4 등 정상적으로 생성되는 것으로 확인된다.

(이게 왜 별도로 만들어졌는지는 모르겠다....)

 

Rockchip AP 프로그래밍 시에는 위의 설명과 같이 ROM Code 프로그램과 Secondary BootLoader 부분에 유의하여 진행하면 다른 문제점 없이 정상 동작됨을 확인할 수 있다.

 

파일시스템 관련은 init 시퀀스이므로 Ubuntu, Debian, Buildroot(Busybox)등의 종류 상관없이 모두 정상적으로 Rootfs 파티션에 위치해있고 'init=' 과 파일시스템 마운트 옵션만 지정한다면 정상 동작 할 것이므로 생략한다.

 

 

 

 

 

 

 

 

 

 

 

 

참고 : http://opensource.rock-chips.com/wiki_Boot_option

'System Programming > Arm' 카테고리의 다른 글

라즈베리파이(RPI)4 OpenOCD JTAG 연결 디버깅 방법  (0) 2023.06.18
ARM Processor 7개의 Mode  (0) 2016.08.29
ARM Processor 개요  (0) 2016.08.29

ARM Processor 7개의 Mode


  • ARM Processor Modes of Operation


  ARM 프로세서에는 7개의 동작 모드가 있습니다. 동작 모드는 프로세서가 어떠한 권한을 가지고 어떠한 일을 처리하고 있는지 나타내는 Processor의 동작 상태를 말합니다. 각각의 7개의 모드는 따로 SP(Stack Point)를 갖고 있습니다. (User와 System은 같은 Stack을 사용, 6개의 Stack이 존재합니다.)

  각각의 모드는 아래의 표와 같습니다.

Mode

Description

CPSR M[4:0]

User

User Task나 Application을 수항 할때의 동작모드로 모든 동작모드 중 유일하게 비특권 모드이다. User Mode는 메모리, I/O장치와 같은 시스템 자원을 사용하는데 제한을 두어 사용자의 실수를 방지한다. 다른 모드(SVC)로 이동하기 위한 방법으로는 소프트웨어 인터럽트를 발생시킨다.

10000b 

 FIQ(Fast IRQ)

 2개의 인터럽트 소스 중 아주 빠르게 인터럽트를 처리할 수 있도록 구성된 모드이다. 빠른 처를 위해서 Exception Vector에서도 최하단에 존재하고 별도의 레지스터를 소유한다.

10001b 

IRQ(Interrupt Request)

 일반적으로 사용되는 인터럽트로 외부 장치에서 요청되는 하드웨어적인 IRQ의 발생에 의해 ARM Core는 IRQ모드로 전환하고 인터럽트를 처리한다.

10010b 

SVC(Superviser)

 대부분의 시스템 자원을 자유롭게 관리할 수 있는 동작모드로 주로 커널이나 디바이스 드라이버를 처리할 때(System Call) 동작되는 모드이다.

Reset 신호 입력 시 및 SWI가 발생하면 SVC Mode로 전환된다.

10011b 

Abort

 메모리에서 명령을 읽거나 데이터를 읽거나 쓸때 오류가 발생할 때 Abort Mode로 전환하여 오류를 처리한다. 커널등의 패닉시 Abort Mode로 전환되어 스택 내용이 전달됨을 알 수 있다.

10111b 

Undefined

 명령어를 읽어 실행하고자 하나 읽어온 명령이 디코더에 정의되어 있지 않은 명령인 경우 발생되는 오류를 처리하는 모드이다.

11011b 

System

 User Mode와 동일한 Register를 사용하고 동일한 용도로 사용된다.
User Mode와의 차이점은 특권을 갖고 있다는 것이다. (ex : OS Kernel)

11111b 



  위 표에 표시된 우측의 5bit 데이터는 CPSR이라는 Status Register 내에 저장된 데이터입니다. CPSR은 현재 Mode의 Status를 저장하고 있는데, 그중 우측 0~4bit인 총 5bit는 각 모드의 정보를 담고 있습니다. 32bit 중 나머지 bit는 N, Z, C, V로 컨디션 코드 및 기타 정보로 사용됩니다.

  인터럽트나 에러 또는 프로그래머에 의하여 모드가 변경될 수 있습니다. Exception에 의하여 모드가 변경되기에, 각 모드의 특징에 대해 알아야만 합니다.

 Abort, FIQ, IRQ 이외의 System, User, SVC Mode가 상호 모드 전환 간의 명확한 정의가 필요합니다.
System과 User는 같은 Stack을 사용하여 거의 Mode간의 차이가 없으나, User Mode에서는 Device등의 자원을 사용하는데 제한이 주어집니다. SVC Mode는 처음 Reset되었을 때 접근하는 Mode로 시스템 자원을 자유롭게 관리가 가능하며 별도의 Stack을 이용합니다.

User Mode는 OS상에 올라가는 Application의 수행 시,
OS는 기본적으로 System Mode이며,
OS나 Application이 시스템 자원(I/O등의 장치)을 사용할때에는 SVC Mode로 전환 후 사용됩니다.

  이때, SVC Mode로 전환되어 시스템 자원을 사용할 권한을 얻는 것을 OS에서는 System Call이라 말합니다. OS에서 Application에 필요한 메모리 공간과 OS의 실행에 따른 메모리 공간을 분리시켜 관리합니다. Application에서 접근 권한이 없는 시스템 자원에 접근하게 허용하기 위해 운영체제는 프로세서가 제공하는 Mode 전환을 System Call Function으로 정의하여 개발자에게 제공합니다.


  ARM Core의 default Mode는 SVC Mode 입니다. SVC에서 출발하여 boot up시에 ARM Core에 대한 모든 권한을 행사할 수 있어야 하기 때문입니다. U-Boot 등의 부트로더 등의서 ARM Core 부팅 및 리셋 시 SVC 모드로 Start.S 등의 어셈블리어 코드가 실행됨을 알 수 있습니다.

 

'System Programming > Arm' 카테고리의 다른 글

라즈베리파이(RPI)4 OpenOCD JTAG 연결 디버깅 방법  (0) 2023.06.18
Rockchip AP 부팅 흐름 정리  (0) 2020.03.11
ARM Processor 개요  (0) 2016.08.29

ARM Processor 개요


  • ARM


  영국의 ARM(Advanced RISC Machines)회사는 RISC 프로세서를 설계하고 라이센싱하는 회사입니다. 그 중에서 ARM社의 주요 사항은 ARM 아키쳐를 개발 및 CPU를 디자인 합니다. ARM Processor는 로드 및 저장 아키텍처를 구현하므로 일반적인 RISC 프로세서입니다. 로드 및 저장 명령어만 메모리에 액세스할 수 있고 데이터 처리 명령어는 레지스터 내용에 대해서만 작동합니다. ARM에서 구현된  Thumb®, Thumb-2, NEON™, VFP 또는 Wireless MMX 명령어 등의 여러 명령어들에 의해 정리되고, 실행 됩니다.

  ARM社는 직접 processor 반도체를 제조해 판매하지 않습니다. 대신 설계한 processor를 Intellectual property(IP) 형태로 제공하며, 이에 대한 license를 판매합니다. ARM processor IP는 일종의 설계도로, 여러 반도체 회사들이 여기에 필요한 주변장치들을 추가해 System On Chip(Soc) 형태로 반도체를 제조해 판매합니다.

  ARM processor의 가장 큰 장점은 성능에 비해 전력 소비량이 작다는 것입니다. 때문에 스마트폰이나 PDA 같은 비교적 큰 computing power가 필요하지만 전력 소비량이 작아야 하는 휴대용 기기 또는 임베디드 분야에서 많이 사용합니다.


  • 용어정리


  ARM 관련 자료에는 ARM architecture, ARM core, ARM processor라는 용어들이 자주 나오는데, 각각의 의미는 다음과 같습니다. 먼저, ARM architecture는 명령어, 레지스터 구조, 메모리 구조 등의 processor 기본 구조와 동작원리에 대한 정의를 의미합니다. 그리고 이 ARM architecture에 따라 구현한 processor의 핵십 부분을 ARM core라고 합니다. 

  이 ARM core에 Cache, Memory Management Unit(이하 MMU), Memory Protection Unit(이하 MPU), Tightly Coupled Memory (이하 TCM), Bus Interface Unit (이하 BIU) 등의 핵심 주변장치들을 추가해놓은 것을 ARM processor라고 합니다. 아래 표는 ARM architecture와 그 architecture를 따르는 주요 ARM core 및 그 특징을 보여줍니다.



ARM architecture 별 특징과 구현 ARM core 예
Arichitecture특징Core
ARMv1
  • 첫 ARM processor
  • 26 bit addressing

ARM1

ARMv2
  • 32 bit multiplier
  • 32 bit coprocessor 지원

ARM2

ARMv2a
  • On-Chip cache
  • SWAP 명령 추가

ARM3

ARMv3
  • 32 bit addressing
  • CPSR과 SPSR regisger 분리
  • Mode 추가 (undefined mode, abort mode)
  • MMU 지원 (가상 메모리)

ARM6 ARM7DI

ARMv3M
  • Signed/unsigned long multiply 명령 추가

ARM7M

ARMv4
  • Signed/unsigned halfwords/bytes load-store 명령 추가
  • Mode 추가 (system mode)
  • 26 bit addressing 지원하지 않음
StrongARM
ARMv4T
  • Thumb 명령 추가

ARM7TDMI ARM9TDMI

ARMv5TE
  • ARMv4T의 확장판
  • 향상된 ARM과 Thumb state 간 전환 명령 추가
  • Digital Signal Processing(이하 DSP) 성능 향상을 위한 명령 추가 (Enhanced DSP 명령)

ARM9E ARM10E

ARMv5TEJ
  • Java bytecode 성능 향상을 위한 명령 추가 (Jazelle 기술)

ARM7EJ ARM926EJ

ARMv6
  • Multiprocessor 명령 개선
  • Unaligned and mixed endian 데이터 처리 지원
  • Multimedia 명령 추가

ARM11



ARM core는 특징에 따라 몇 개의 family로 구분합니다. 아래 표는 ARM core family와 그 familiy에 속하는 ARM core의 대략적인 특징을 보여줍니다.




ARM core family 별 특징
ARM7ARM9ARM10ARM11
파이프라인 단계3568
동작 주파수 (MHz)

(Worst case)

125220 ~ 250266 ~ 325400
전력 소비량 (mW/MHz)0.060.4

(+cache)

0.5

(+cache)

0.4

(+cache)

성능 (MIPS/MHz)0.91.11.31.2
구조Von NeumannHarvardHarvardHarvard
  • mW/MHz: 0.13 micron 공정일 경우
  • MIPS: Dhrystone 2.1

ARM architecture, ARM core, 또는 ARM processor 이름에 포함된 알파벳과 숫자의 의미는 다음과 같습니다.



ARM [a] [b] [T] [D] [M] [I] [E] [J] [F] [-S]

a : 속한 ARM core family

b : memory management unit, memory protection unit, cache, TCM 구성

T : Thumb 명령 지원

D : 디버그 기능 지원

M : 64 bit 결과를 내는 곱셈기 지원

I : In-Circuit Emulator(이하 ICE) 기능 지원

E : DSP 성능 향상을 위한 명령(Enhanced DSP 명령) 지원

J : Java bytecode 성능 향상을 위한 명령(Jazelle 기술) 지원

F : Vector Floating-Point(이하 VFP) 구조 지원

-S: Electronic Design Automation(이하 EDA) 도구로 synthesis 할 수 있음


  예를 들어, ARM926EJ-S의 “9”는 ARM 9 family에 속함을, “26”은 MMU와 cache 그리고 TCM 지원을, “E”는 DSP 성능 향상을 위한 명령(Enhanced DSP 명령) 지원을, J는 Java bytecode 성능 향상을 위한 명령(Jazelle 기술) 지원을, -S는 EDA 도구로 synthesis 할 수 있음을 의미합니다. 그리고 ARM 9 family 부터는 대부분 “T”, “D”, “M”, “I” 가 나타내는 기능들을 기본적으로 포함하기 때문에 이 알파벳들은 생략합니다.


  • Programmer's Model


  Programmer's model이란 프로그래머가 프로그램을 작성하기 위해 필요한 정보를 의미합니다. 명령어, 메모리 구조, 동작 모드, 레지스터, 예외처리 방법, 인터럽트 처리 방법 등이 이에 해당합니다. 이 programmer’s model은 ARM architecture에 따라 조금씩 달라집니다.


  • 명령어 집합, 명령어 집합 상태, 동작 모드


ARM Thumb instruction set


  ARM은 ARM instruction set과 Thumb instruction set이라는 두 종류의 명령어 집합(instruction set)을 지원한다. ARM instruction set에 속하는 명령어들은 모두 32비트 명령어입니다. Thumb instruction set에 속하는 명령어들은 모두 16비트 명령어입니다. 따라서 동일한 C언어 소스코드로 작성된 프로그램을 ARM 명령어 집합으로 컴파일 했을 때와 Thumb 명령어 집합으로 컴파일 했을 때에 생성되는 바이너리 이미지의 크기는 이상적인 상황에서 ARM 명령어 집합으로 컴파일하는 것이 Thumb 명령어 집합으로 컴파일 하는 것 보다 정확히 두 배 커야 합니다.

  물론 여러가지 최적화 기법을 컴파일러가 적용하기 때문에 실제로 ARM 명령어 집합의 바이너리 이미지가 Thumb 명령어 집합의 바이너리 이미지보다 두 배 큰 경우는 거의 생기지 않습니다.


Instruction Set State


  ARM은 명령어 집합이 두 개이므로 해당 명령어 집합을 실행하는 프로세서의 상태도 이에 연동되어 두 개이다. ARM 명령어 집합에 해당하는 32비트 명령어를 실행하는 프로세서의 상태를 ARM 상태(ARM state)라고 하고 마찬가지로, Thumb 명령어 집합에 해당하는 16비트 명령어를 실행하는 상태를 Thumb 상태(Thumb state)라고 합니다.

  당연히 ARM 상태와 Thumb 상태는 프로세서가 동작하는 중간에 서로 변경 가능하다. ARM 명령어 중에 BX나 BLX를 사용해서 ARM 상태에서 Thumb 상태로 변경할 수 있습니다. 서로 상태를 바꾸어가며 변경이 가능할 뿐 두 명령어 집합을 섞어서 사용할 순 없습니다. ARM 상태에서 ARM 명령어 집합의 명령을 수행하다가 Thumb 명령어가 나오면 반드시 프로세서의 명령어 수행 상태를 변경해야 합니다.

Cortex-A5에서는 추가로 두 가지 프로세서 상태가 더 존재 하는데, ThumbEE 상태와 Jazelle 상태입니다. ThumbEE 상태는 Thumb 명령어와는 다른 명령어로 프로그램이 실행되는 중간에 실행코드를 만들어 내며, 일종의 바이트 코드의 형태라고 보면 됩니다. Jazelle 상태는 1바이트 단위로 정렬되고 길이가 가변적인 Java 바이트 코드를 해석하기 위한 프로세서 동작 상태입니다.




+ Recent posts