<超標量處理器概覽> 第 3 章 - 虛擬存儲器

3.2 - 位址轉換 3.2.3 - Page Fault PTE (page table entry) 中包含: valid bit:標記這個 PTE 是否有效,當作業系統設定好 page table 後,就需要將對應 PTE 的 valid bit 設成 1 dirty bit:當一個 page 內容被更新時 (e.g. 執行 store 指令),硬體會自動將 dirty bit 設成 1,代表這個 page 如果被選中要被替換時,需要將 page 的內容 swap 回硬碟 access bit:當一個 page 被訪問 (load/store) 時,硬體會自動將 access bit 設成 1,作業系統則會定期的將 access bit 清為 0 當要替換 page table 時,就可以根據 access bit 來得知最近 page 是否有被訪問過,進而實現近似 LRU (Least Recently Used) 的替換策略 當 page 要被 swapped out 前: 要先將 D-Cache 的內容 flush 進 memory,以確保要被 swapped out 的 page 內容是最新的 將 PTE 的 valid bit 設成 0,以避免其他 CPU 或 MMU 在接下來 swap out 期間誤讀這個 page valid bit 為 0 時,代表該 page 並不存在記憶體中,當 MMU 存取時會觸發 page fault 讓作業系統從硬碟讀取 page 進記憶體 valid bit 是由作業系統在把 page 讀進記憶體後設為 1 的 執行如 RISC-V 的 sfence.vma 指令,確保 TLB 中對應的 entries 被清空,以避免 MMU 錯誤存取到被 swapped out page 的 cache 內容 3.4 - 加入 TLB 和 Cache 3.4.1 - TLB 的設計 TLB entry 中也會包含: valid bit:標記這個 TLB entry 是否有效;當 TLB entry 被 swap in 時,valid bit 會被設為 1 dirty bit:當執行 store 指令時,如果 TLB hit,就不會再訪問 PTE,也就不會更新 PTE 中的 dirty bit;只有當 TLB entry 要被替換時,才會同步至 PTE access bit:當執行 load/store 指令時,如果 TLB hit,就不會再訪問 PTE,也就不會更新 PTE 中的 access bit;只有當 TLB entry 要被替換時,才會同步至 PTE P.S. RISC-V 中,TLB entry 並沒有包含 dirty 和 access bits,作業系統需要自己確保 PTE 的更新 sfence.vma 只會將 TLB 中對應的 entries 給 invalid 而已 (針對 TLB 的部份) 如果 CPU 有支援 Svadu extension,那麼硬體就會自動更新 PTE 中的 dirty 和 access bit 如果硬體支援 access 和 dirty bits 自動更新, 作業系統只需要讀取 PTE 即可獲得最新的 access 和 dirty bits 的狀態 如果硬體不支援 access 和 dirty bits 自動更新,當 access=0 或 dirty=0 時: MMU 會觸發 page fault 作業系統在 page fault handler 中設置 access=1 或 dirty=1 作業系統得先透過設定 PTE 的 valid bit (追蹤 page 是否有被 accessed) 和 disable write permission (追蹤 page 是否有被 stored) 來當 page fault 發生時,在 page fault handler 更新 access 或 dirty bit 重新執行造成 page fault 的那道指令 也就是讓作業系統來自行管理 PTE 中 access 和 dirty bits 的內容,效能較差,但硬體設計較簡單 RISC-V privilege spec: If pte.a=0, or if the original memory access is a store and pte.d=0: If the Svade extension is implemented, stop and raise a page-fault exception corresponding to the original access type. 一般為了減少 TLB miss rate,會使用 fully-associative 來設計 TLB 缺點:TLB 容量不能太大,不然會增加查找的時間 因此,有些架構也會使用 set-associative 來設計容量比較大的 TLB 因為是 fully-associative 或 set-associative,因此 TLB entry 中,還會包含 tag 欄位 (VPN,Virtual Page Number),用來比對 TLB 是否 hit P.S. TLB entry 中的 data 是 PFN (Page Frame Number) 現代處理器架構,通常都使用 2-level TLB 1st level 採用 Harvard 架構,分為 I-TLB (指令) 和 D-TLB (數據),一般採用 fully-associative 設計 2nd level 採用 Von Neumann 架構,指令和數據共用,一般採用 set-associative 設計 現代處理器中因應程式的 size 越來越大,因此還會支持容量更大的 page E.g. 128 entries 的 TLB,只能映射到 128 * 4KB = 512 MB 大小的程式,顯然不夠用 更大的 page: 優點: 降低 TLB miss rate 缺點: 當發生 page fault 的時候,需要花更多的時間才能將更大的 page 內容從硬碟搬至記憶體 如果程式用不到這麼大的 page,那麼空間就被浪費了,且也會造成 page fragment,降低 page 的使用效率 總和以上原因,現代處理器都支持大小可變的 page,由作業系統負責管理,根據程式的特點選用不同大小的 page,最大程度地利用 TLB 有限的空間,並降低 page fragment 在 TLB 中會有相對應的設定可以調整映射的 page 大小 因為記憶體的存取速度相對於 CPU 的執行速度來說非常慢,因此 TLB 通常只會採用 Write-back 的方式設計,且因為 TLB entry 中 tag 和 data 等欄位是不會變動的,因此當發生 TLB miss,需要將 TLB entry swap out 時,只需要將 dirty bit (執行 store 指令時會被更新) 和 access bit (執行 load/store 指令時會被更新) 寫回 PTE 中即可 TLB miss 發生的情況: Page 並不在記憶體中,因此也不在 TLB 中 Page 在記憶體中,page table 中也有對應的 PTE,但這個 PTE 並沒有被 cache 在 TLB 中 Page 在記憶體中,page table 中也有對應的 PTE,這個 PTE 也曾經存在 TLB 中,但是因為先前的 TLB miss,因此被替換出來了 TLB miss 時,TLB entry 的替換策略: LRU (Least Recently Used) Random:如同 cache,使用一個 counter,每個 cycle 都會 + 1,每次 TLB miss 要替換 TLB entry 時,就根據當下 counter 的值決定是哪個 entry 要被替換 LRU 比較難實現,所以通常會採用 Random 的替換策略,實做也比較簡單 如果 TLB 採用 Write-back,TLB entry 中的 dirty 和 access bits 就有可能跟 PTE 的內容不同步,如果 page 要被 swap out 的時候,作業系統會無法即時得知究竟 page 是否 dirty,以及是否有被 accessed 過 直觀解法:每次發生 page fault 要 swap page 的時候,先將 TLB entries flush 至 PTE 缺點:需要耗費額外的時間 flush TLB 另類解法:作業系統可以認為,有在 TLB 中被 cached 對應的 pages 都是正在使用的,因此不能將其 swap out 需要作業系統自行維護一張表,紀錄哪些 PTE 有被 cached 在 TLB 中,且為 valid 的 不過這種設計並不常見 如果系統中有 D-Cache,page 也是 dirt 的,那麼 page 被更動的內容也有可能還存在 D-Cache 中,因此在 page swap out 前也必須先 flush D-Cache 3.4.2 - Cache 的設計 兩種 caches: ...

2025/02/27 · 7 分鐘 · 1362 字 · Frank Chang

<超標量處理器概覽> 第 2 章 - Cache

2.1 - Cache 的一般設計 L1 Cache 通常分為 I-Cache 和 D-Cache,為 private cache I-Cache 需要能夠在一個 cycle 內,讀取多條的指令 D-Cache 需要能在一個 cycle 內,處理多條 load/store 指令的訪問,因此需要使用 multi-port 的設計 Instruction fetch 的時候,會向 L1 Cache 嘗試讀取指令,因此 L1 Cache 也是 pipeline 的一部分 L1 Cache 必須保持跟 CPU 相近的速度,因此通常是透過 SRAM 實現的 L2 Cache 通常都是指令和資料一起 share 整個 cache,容量通常以 MB 為單位 現在大部份的 multi-core 都是 shared 同一個 L3 Cache L2 Cache 被訪問的頻率通常不是很高 (L1 Cache miss 才會訪問 L2 Cache),因此不需要使用 multi-port 的設計 需要盡可能的提昇 L2 Cache 的命中率,因為如果 L2 Cache miss,且沒 L3 Cache 的話,就需要去訪問 DRAM 了,訪問時間會很長 2.1.1 - Cache 的組成方式 Set-associative cache: ...

2025/02/13 · 11 分鐘 · 2185 字 · Frank Chang

<超標量處理器概覽> 第 1 章 - 超標量處理器概覽

1.2 - 普通處理器的流水線 1.2.2 - 流水線的劃分 Pipeline CPU,其 cycle time 由最長 cycle time 的 pipeline stage 所決定 因此,最好每個 pipeline stage 的 cycle time 都是差不多長的 解決各個 pipeline stage cycle time 不平衡的方法: 合: 將多個 pipeline stages 合併成一個 stage,例如: Fetch (7 ns) & Decode (3 ns) | Operand fetch (8 ns) & Execute (5 ns) | Memory (10 ns) & Write back (3 ns) 此方法將 pipeline stages 從 5 個降為 3 個,原本各個 pipeline stage cycle time 不平衡的情況也變成:10 ns | 13 ns | 13 ns 適用於對於性能要求不高的 CPU,例如:ARM7、ARM9、Cortex-M0、Cortex-M3 因為 pipeline stage 的 cycle time 增加了,從原本的 max(7 ns, 3 ns, 8 ns, 5 ns, 10 ns, 3 ns) ⇒ 10 ns,增加為 max(10 ns, 13 ns, 13 ns) ⇒ 13 ns,cycle time 增加,就代表 CPU 的 frequency 會降低 拆: 將 pipeline stage 拆成更小的 stages,例如: Fetch (7 ns) ⇒ Fetch1 (3.5 ns) & Fetch2 (3.5 ns) 適用於高性能 CPU,因為可以提昇 CPU 的 frequency 缺點: 增加所需的硬體元件,例如:需要多個 pipeline registers 功耗會增大 較深的 pipeline 也會增加 branch misprediction 的 penalty 1.2.3 - 指令間的相依性 Instructions hazard: ...

2025/02/12 · 4 分鐘 · 658 字 · Frank Chang

AMD PetaLinux + OpenAMP Demos

⚠️ This demo is based on PetaLinux 2024.1 release. PetaLinux Tools Documentation: Reference Guide (UG1144) (2024.1 English) Download 2024.1 PetaLinux Tools Installer & ZCU102 BSP: https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/embedded-design-tools/2024-1.html Switch to bash: 1 $ bash Set $SHELL to /bin/bash: 1 $ export SHELL=/bin/bash Disable dash as the default system shell (/bin/sh): 1 $ sudo dpkg-reconfigure dash Select: Install PetaLinux Tools Installer: ...

2025/01/17 · 3 分鐘 · 623 字 · Frank Chang

Linux Kernel: SBI MPXY

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/linux/-/tree/dev-optee-mpxy Commit ID: df5dc01764820f113312f7a39f221b49985bbd7a 1 2 3 4 5 6 7 8 9 10 // arch/riscv/kernel/mpxy-sbi.c struct sbi_mpxy { void *shmem; phys_addr_t shmem_phys_addr; bool active; }; // Define per-cpu varible: sbi_mpxy. DEFINE_PER_CPU(struct sbi_mpxy, sbi_mpxy); do_initcalls() … sbi_mpxy_init() sbi_mpxy_init() Check SBI version and if MPXY extension is supported by OpenSBI. ...

2024/12/31 · 2 分鐘 · 344 字 · Frank Chang

Linux Kernel: OP-TEE Supplicant

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/linux/-/tree/dev-optee-mpxy Commit ID: df5dc01764820f113312f7a39f221b49985bbd7a TEE supplicant /etc/init.d/S30-tee-supplicant /dev/teepriv0 main() process_one_request() process_one_request() read_request() Issue TEE_IOC_SUPPL_RECV ioctl to receive the TEE supplicant request from OP-TEE. This will be blocked until TEE supplicant request is received. Spawn a new thread to process for the new request: thread_main() process_one_request() The original thread will continue to handle the TEE supplicant request from OP-TEE: If RPC command: OPTEE_MSG_RPC_CMD_LOAD_TA: Call load_ta() to load the TA according to the UUID. load_ta() will call TEECI_LoadSecureModule() to load the TA. TEECI_LoadSecureModule() will call fopen(), ftell() to open TA and get the size of TA. If the buffer size (ta_size) is not enough to hold the TA, return the required size to let the caller increase the buffer size and try again. Otherwise, call fread() to read TA and save it to the buffer. … Call write_response() to send the TEE supplicant response to OP-TEE. write_response() Issue TEE_IOC_SUPPL_SEND ioctl to send the TEE supplicant response to OP-TEE. This will unblock Call wait_for_completion_interruptible(**&req->c**) to wait for TEE supplicant to process.

2024/12/28 · 1 分鐘 · 174 字 · Frank Chang

Linux Kernel: OP-TEE

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/linux/-/tree/dev-optee-mpxy Commit ID: df5dc01764820f113312f7a39f221b49985bbd7a OP-TEE provides a pseudo Trusted Application (PTA): drivers/tee/optee/device.c in order to support device enumeration. In other words, OP-TEE driver invokes this application to retrieve a list of Trusted Applications which can be registered as devices on the TEE bus. 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 // drivers/tee/optee/optee_private.h /** * struct optee - main service struct * @supp_teedev: supplicant device * @teedev: client device * @ops: internal callbacks for different ways to reach secure * world * @ctx: driver internal TEE context * @smc: specific to SMC ABI * @ffa: specific to FF-A ABI * @call_queue: queue of threads waiting to call @invoke_fn * @notif: notification synchronization struct * @supp: supplicant synchronization struct for RPC to supplicant * @pool: shared memory pool * @rpc_param_count: If > 0 number of RPC parameters to make room for * @scan_bus_done flag if device registation was already done. * @scan_bus_work workq to scan optee bus and register optee drivers */ struct optee { struct tee_device *supp_teedev; struct tee_device *teedev; const struct optee_ops *ops; struct tee_context *ctx; union { struct optee_smc smc; struct optee_ffa ffa; }; struct optee_shm_arg_cache shm_arg_cache; struct optee_call_queue call_queue; struct optee_notif notif; struct optee_supp supp; struct tee_shm_pool *pool; unsigned int rpc_param_count; bool scan_bus_done; struct work_struct scan_bus_work; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // drivers/tee/optee/optee_private.h /** * struct optee_ops - OP-TEE driver internal operations * @do_call_with_arg: enters OP-TEE in secure world * @to_msg_param: converts from struct tee_param to OPTEE_MSG parameters * @from_msg_param: converts from OPTEE_MSG parameters to struct tee_param * * These OPs are only supposed to be used internally in the OP-TEE driver * as a way of abstracting the different methogs of entering OP-TEE in * secure world. */ struct optee_ops { int (*do_call_with_arg)(struct tee_context *ctx, struct tee_shm *shm_arg, u_int offs, bool system_thread); int (*to_msg_param)(struct optee *optee, struct optee_msg_param *msg_params, size_t num_params, const struct tee_param *params); int (*from_msg_param)(struct optee *optee, struct tee_param *params, size_t num_params, const struct optee_msg_param *msg_params); }; do_initcalls() … optee_core_init() optee_core_init() ...

2024/12/26 · 10 分鐘 · 1921 字 · Frank Chang

Linux Kernel: TEE

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/linux/-/tree/dev-optee-mpxy Commit ID: df5dc01764820f113312f7a39f221b49985bbd7a Kernel provides a TEE bus infrastructure where a Trusted Application is represented as a device identified via Universally Unique Identifier (UUID) and client drivers register a table of supported device UUIDs. 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 // include/linux/tee_core.h /** * struct tee_device - TEE Device representation * @name: name of device * @desc: description of device * @id: unique id of device * @flags: represented by TEE_DEVICE_FLAG_REGISTERED above * @dev: embedded basic device structure * @cdev: embedded cdev * @num_users: number of active users of this device * @c_no_user: completion used when unregistering the device * @mutex: mutex protecting @num_users and @idr * @idr: register of user space shared memory objects allocated or * registered on this device * @pool: shared memory pool */ struct tee_device { char name[TEE_MAX_DEV_NAME_LEN]; const struct tee_desc *desc; int id; unsigned int flags; struct device dev; struct cdev cdev; size_t num_users; struct completion c_no_users; struct mutex mutex; /* protects num_users and idr */ struct idr idr; struct tee_shm_pool *pool; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // include/linux/tee_core.h /** * struct tee_desc - Describes the TEE driver to the subsystem * @name: name of driver * @ops: driver operations vtable * @owner: module providing the driver * @flags: Extra properties of driver, defined by TEE_DESC_* below */ #define TEE_DESC_PRIVILEGED 0x1 struct tee_desc { const char *name; const struct tee_driver_ops *ops; struct module *owner; u32 flags; }; 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 // include/linux/tee_core.h /** * struct tee_driver_ops - driver operations vtable * @get_version: returns version of driver * @open: called when the device file is opened * @release: release this open file * @open_session: open a new session * @close_session: close a session * @system_session: declare session as a system session * @invoke_func: invoke a trusted function * @cancel_req: request cancel of an ongoing invoke or open * @supp_recv: called for supplicant to get a command * @supp_send: called for supplicant to send a response * @shm_register: register shared memory buffer in TEE * @shm_unregister: unregister shared memory buffer in TEE */ struct tee_driver_ops { void (*get_version)(struct tee_device *teedev, struct tee_ioctl_version_data *vers); int (*open)(struct tee_context *ctx); void (*release)(struct tee_context *ctx); int (*open_session)(struct tee_context *ctx, struct tee_ioctl_open_session_arg *arg, struct tee_param *param); int (*close_session)(struct tee_context *ctx, u32 session); int (*system_session)(struct tee_context *ctx, u32 session); int (*invoke_func)(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param); int (*cancel_req)(struct tee_context *ctx, u32 cancel_id, u32 session); int (*supp_recv)(struct tee_context *ctx, u32 *func, u32 *num_params, struct tee_param *param); int (*supp_send)(struct tee_context *ctx, u32 ret, u32 num_params, struct tee_param *param); int (*shm_register)(struct tee_context *ctx, struct tee_shm *shm, struct page **pages, size_t num_pages, unsigned long start); int (*shm_unregister)(struct tee_context *ctx, struct tee_shm *shm); }; 1 2 3 4 5 6 7 8 9 10 11 // include/linux/tee_core.h /** * struct tee_shm_pool - shared memory pool * @ops: operations * @private_data: private data for the shared memory manager */ struct tee_shm_pool { const struct tee_shm_pool_ops *ops; void *private_data; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // include/linux/tee_core.h /** * struct tee_shm_pool_ops - shared memory pool operations * @alloc: called when allocating shared memory * @free: called when freeing shared memory * @destroy_pool: called when destroying the pool */ struct tee_shm_pool_ops { int (*alloc)(struct tee_shm_pool *pool, struct tee_shm *shm, size_t size, size_t align); void (*free)(struct tee_shm_pool *pool, struct tee_shm *shm); void (*destroy_pool)(struct tee_shm_pool *pool); }; 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 // include/linux/tee_drv.h /** * struct tee_context - driver specific context on file pointer data * @teedev: pointer to this drivers struct tee_device * @data: driver specific context data, managed by the driver * @refcount: reference counter for this structure * @releasing: flag that indicates if context is being released right now. * It is needed to break circular dependency on context during * shared memory release. * @supp_nowait: flag that indicates that requests in this context should not * wait for tee-supplicant daemon to be started if not present * and just return with an error code. It is needed for requests * that arises from TEE based kernel drivers that should be * non-blocking in nature. * @cap_memref_null: flag indicating if the TEE Client support shared * memory buffer with a NULL pointer. */ struct tee_context { struct tee_device *teedev; void *data; struct kref refcount; bool releasing; bool supp_nowait; bool cap_memref_null; }; do_initcalls() … tee_init() tee_init() ...

2024/12/24 · 5 分鐘 · 1009 字 · Frank Chang

OpenSBI: MPXY

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/opensbi/-/tree/dev-optee-mpxy Commit ID: 7d4c90953afe3bd86f9e2501bd4c2501e8db1898 init_coldboot() sbi_mpxy_init() sbi_mpxy_init() Allocate struct mpxy_state for each domain. sbi_platform_mpxy_init() Call platform-defined mpxy_init(). e.g. For generic platform: fdt_mpxy_init(). fdt_mpxy_init() Iterate MPXY drivers in fdt_mpxy_drivers[]. MPXY drivers are generated at compile time: 1 2 3 4 5 6 7 8 9 10 11 // lib/utils/mpxy/objects.mk libsbiutils-objs-$(CONFIG_FDT_MPXY) += mpxy/fdt_mpxy.o libsbiutils-objs-$(CONFIG_FDT_MPXY) += mpxy/fdt_mpxy_drivers.o carray-fdt_mpxy_drivers-$(CONFIG_FDT_MPXY_RPMI_MBOX) += fdt_mpxy_rpmi_mbox libsbiutils-objs-$(CONFIG_FDT_MPXY_RPMI_MBOX) += mpxy/fdt_mpxy_rpmi_mbox.o carray-fdt_mpxy_drivers-$(CONFIG_FDT_MPXY_MM) += fdt_mpxy_mm libsbiutils-objs-$(CONFIG_FDT_MPXY_MM) += mpxy/fdt_mpxy_mm.o carray-fdt_mpxy_drivers-$(CONFIG_FDT_MPXY_OPTEED) += fdt_mpxy_opteed libsbiutils-objs-$(CONFIG_FDT_MPXY_OPTEED) += mpxy/fdt_mpxy_opteed.o 1 2 3 4 5 // lib/utils/mpxy/fdt_mpxy_drivers.carray HEADER: sbi_utils/mpxy/fdt_mpxy.h TYPE: struct fdt_mpxy NAME: fdt_mpxy_drivers 1 2 3 4 5 6 7 // include/sbi_utils/mpxy/fdt_mpxy.h struct fdt_mpxy { const struct fdt_match *match_table; int (*init)(void *fdt, int nodeoff, const struct fdt_match *match); void (*exit)(void); }; Call drv->init(). ...

2024/12/23 · 1 分鐘 · 147 字 · Frank Chang

OpenSBI: OP-TEE

⚠️ The code is based on: https://gitlab.com/riseproject/riscv-optee/opensbi/-/tree/dev-optee-mpxy Commit ID: 7d4c90953afe3bd86f9e2501bd4c2501e8db1898 mpxy_opteed_init() Match compatible string: "riscv,sbi-mpxy-opteed". Allocate channel. opteed_domain_setup() Setup domain for OP-TEE dispatcher by looking up the domain specified by opensbi-domain-instance phandle in DTS. Assign the domain name to opteed_domain_name. Get channel ID from DTS property: riscv,sbi-mpxy-channel-id. Initialize channel: 1 2 3 4 5 6 // lib/utils/mpxy/fdt_mpxy_opteed.c channel->channel_id = channel_id; channel->send_message = mpxy_opteed_send_message; channel->attrs.msg_proto_id = SBI_MPXY_MSGPROTO_TEE_ID; channel->attrs.msg_data_maxlen = PAGE_SIZE; Register channel: sbi_mpxy_register_channel() ...

2024/12/21 · 3 分鐘 · 470 字 · Frank Chang