{"id":29675839,"url":"https://github.com/oxidecomputer/qemu-systick-bug","last_synced_at":"2025-07-22T23:38:52.996Z","repository":{"id":38334602,"uuid":"254937932","full_name":"oxidecomputer/qemu-systick-bug","owner":"oxidecomputer","description":"Program demonstrating bug in QEMU's SysTick emulation","archived":false,"fork":false,"pushed_at":"2022-06-06T19:18:52.000Z","size":204,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-02-10T00:58:10.078Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oxidecomputer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-11T19:15:34.000Z","updated_at":"2024-12-18T09:19:54.000Z","dependencies_parsed_at":"2022-07-25T20:31:03.948Z","dependency_job_id":null,"html_url":"https://github.com/oxidecomputer/qemu-systick-bug","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oxidecomputer/qemu-systick-bug","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fqemu-systick-bug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fqemu-systick-bug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fqemu-systick-bug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fqemu-systick-bug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oxidecomputer","download_url":"https://codeload.github.com/oxidecomputer/qemu-systick-bug/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fqemu-systick-bug/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266591233,"owners_count":23953082,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-22T23:38:52.262Z","updated_at":"2025-07-22T23:38:52.984Z","avatar_url":"https://github.com/oxidecomputer.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# qemu-systick-bug\n\nThis repository demonstrates a bug with respect to QEMU's handling of\nSysTick on ARM -- or at the very least, an inconsistency with respect to\nhardware.  (This issue has been filed with QEMU as\n\u003ca href=\"https://bugs.launchpad.net/qemu/+bug/1872237\"\u003e#1872237\u003c/a\u003e.)\n\n## Issue\n\nTake this Rust program:\n\n```rust\n#![no_std]\n#![no_main]\n\nextern crate panic_semihosting;\n\nuse cortex_m_rt::entry;\nuse cortex_m_semihosting::hprintln;\nuse cortex_m::peripheral::syst::SystClkSource;\nuse cortex_m::peripheral::SYST;\n\nfn delay(syst: \u0026mut cortex_m::peripheral::SYST, ms: u32)\n{\n    /*\n     * Configured for the LM3S6965, which has a default CPU clock of 12 Mhz\n     */\n    let reload = 12_000 * ms;\n\n    syst.set_reload(reload);\n    syst.clear_current();\n    syst.enable_counter();\n\n    hprintln!(\"waiting for {} ms (SYST_CVR={}) ...\",\n        ms, SYST::get_current()\n    ).unwrap();\n\n    while !syst.has_wrapped() {}\n\n    hprintln!(\"  ... done (SYST_CVR={})\\n\", SYST::get_current()).unwrap();\n\n    syst.disable_counter();\n}\n\n#[entry]\nfn main() -\u003e ! {\n    let p = cortex_m::Peripherals::take().unwrap();\n    let mut syst = p.SYST;\n\n    syst.set_clock_source(SystClkSource::Core);\n\n    loop {\n        delay(\u0026mut syst, 1000);\n        delay(\u0026mut syst, 100);\n    }\n}\n```\n\nThis program should oscillate between waiting for one second and waiting\nfor 100 milliseconds.  Under hardware, this is more or less what it does\n(depending on core clock frequency); e.g., from an STM32F4107 (connected via\nOCD and with semi-hosting enabled):\n\n```\nwaiting for 1000 ms (SYST_CVR=11999949) ...\n  ... done (SYST_CVR=11999902)\n\nwaiting for 100 ms (SYST_CVR=1199949) ...\n  ... done (SYST_CVR=1199897)\n\nwaiting for 1000 ms (SYST_CVR=11999949) ...\n  ... done (SYST_CVR=11999885)\n\nwaiting for 100 ms (SYST_CVR=1199949) ...\n  ... done (SYST_CVR=1199897)\n\nwaiting for 1000 ms (SYST_CVR=11999949) ...\n  ... done (SYST_CVR=11999885)\n\n```\n\nUnder QEMU, however, its behavior is quite different:\n\n```\n$ cargo run\n    Finished dev [unoptimized + debuginfo] target(s) in 0.03s\n     Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/debug/qemu-systick-bug`\nwaiting for 1000 ms (SYST_CVR=11999658) ...\n  ... done (SYST_CVR=11986226)\n\nwaiting for 100 ms (SYST_CVR=0) ...\n  ... done (SYST_CVR=1186560)\n\nwaiting for 1000 ms (SYST_CVR=1185996) ...\n  ... done (SYST_CVR=11997350)\n\nwaiting for 100 ms (SYST_CVR=0) ...\n  ... done (SYST_CVR=1186581)\n```\n\nIn addition to the values being strangely wrong, the behavior is wrong:\nthe first wait correctly waits for 1000 ms -- but the subsequent wait\n(which should be for 100 ms) is in fact 1000 ms, and the next wait (which\nshould be for 1000 ms) is in fact 100 ms.  (That is, it appears as if\nthe periods of the two delays have been switched.)\n\nThe problems is that the QEMU ARM emulation code does not reload SYST_CVR from\nSYST_RVR if SYST_CSR.ENABLE is not set -- and moreover, that SYST_CVR is\nnot reloaded from SYST_RVR even when SYST_CSR.ENABLE becomes set.  This is\nvery explicit; from\n\u003ca\nhref=\"https://github.com/qemu/qemu/blob/8bac3ba57eecc466b7e73dabf7d19328a59f684e/hw/timer/armv7m_systick.c#L42-L60\"\u003ehw/timer/armv7m_systick.c\u003c/a\u003e:\n\n```c\nstatic void systick_reload(SysTickState *s, int reset)\n{\n    /* The Cortex-M3 Devices Generic User Guide says that \"When the\n     * ENABLE bit is set to 1, the counter loads the RELOAD value from the\n     * SYST RVR register and then counts down\". So, we need to check the\n     * ENABLE bit before reloading the value.\n     */\n    trace_systick_reload();\n\n    if ((s-\u003econtrol \u0026 SYSTICK_ENABLE) == 0) {\n        return;\n    }\n\n    if (reset) {\n        s-\u003etick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n    }\n    s-\u003etick += (s-\u003ereload + 1) * systick_scale(s);\n    timer_mod(s-\u003etimer, s-\u003etick);\n}\n```\n\nThe statement in the code is stronger than the statement in the\n\u003ca href=\"https://static.docs.arm.com/ddi0403/eb/DDI0403E_B_armv7m_arm.pdf\"\u003eARMv7-M Architecture Reference Manual\u003c/a\u003e (sec B3.3.1):\n\n\u003e Writing to SYST_CVR clears both the register and the COUNTFLAG status\n\u003e bit to zero. This causes the SysTick logic to reload SYST_CVR from SYST_RVR\n\u003e on the next timer clock. A write to SYST_CVR does not trigger the\n\u003e SysTick exception logic.\n\nNote that this does not mention the behavior on a write to SYST_CVR when\nSYST_CSR.ENABLE is not set -- and in particular, does *not* say that writes to\nSYST_CVR will be ignored if SYST_CSR.ENABLE is not set.\n\nSection 3.3.1 does go on to say:\n\n\u003e The SYST_CVR value is UNKNOWN on reset. Before enabling the SysTick counter,u\n\u003e software must write the required counter value to SYST_RVR, and then write\n\u003e to SYST_CVR. This clears SYST_CVR to zero. When enabled, the counter \n\u003e reloads the value from SYST_RVR, and counts down from that value, rather]\n\u003e than from an arbitrary value.\n\n(This is more or less what has been quoted in the implementation of\n`systick_reload`, above.)  This note does **not** say, however, that writes\nto SYST_CVR will be discarded when not enabled, but rather that the counting\nwill only begin (and the value in SYST_RVR loaded or reloaded) when\nSYST_CSR.ENABLE becomes set.\n\nWhile QEMU's behavior does not match that of the hardware (and is therefore\nat some level malfunctioning), there is additional behavior that is very\nclearly incorrect: once SYST_CSR.ENABLE is set, not only will SYST_CVR\ncontinue to return 0 (that is, the counter will not be enabled),\nSYST_CSR.COUNTFLAG will become set when the *old* value of SYST_RVR ticks\nhave elapsed!  This is wrong in both regards: if SYST_CVR is not counting\ndown, SYST_CSR.COUNTFLAG should never become set -- and it certainly\nshouldn't match a value of SYST_RVR that has been overwritten in the\ninterim!\n\nIn terms of fixing this, it's helpful to understand the\n\u003ca\nhref=\"https://lists.gnu.org/archive/html/qemu-devel/2015-05/msg01217.html\"\u003econtext\naround this change\u003c/a\u003e:\n\n\u003e Consider the following pseudo code to configure SYSTICK (The\n\u003e recommended programming sequence from \"the definitive guide to the\n\u003e arm cortex-m3\"):\n\u003e    SYSTICK Reload Value Register = 0xffff\n\u003e    SYSTICK Current Value Register = 0\n\u003e    SYSTICK Control and Status Register = 0x7\n\u003e\n\u003e The pseudo code \"SYSTICK Current Value Register = 0\" leads to invoking\n\u003e systick_reload(). As a consequence, the systick.tick member is updated\n\u003e and the systick timer starts to count down when the ENABLE bit of\n\u003e SYSTICK Control and Status Register is cleared.\n\u003e\n\u003e The worst case is that: during the system initialization, the reset\n\u003e value of the SYSTICK Control and Status Register is 0x00000000. \n\u003e When the code \"SYSTICK Current Value Register = 0\" is executed, the\n\u003e systick.tick member is accumulated with \"(s-\u003esystick.reload + 1) *\n\u003e systick_scale(s)\". The systick_scale() gets the external_ref_clock\n\u003e scale because the CLKSOURCE bit of the SYSTICK Control and Status\n\u003e Register is cleared. This is the incorrect behavior because of the\n\u003e code \"SYSTICK Control and Status Register = 0x7\". Actually, we want\n\u003e the processor clock instead of the external reference clock.\n\u003e\n\u003e This incorrect behavior defers the generation of the first interrupt. \n\u003e\n\u003e The patch fixes the above-mentioned issue by setting the systick.tick\n\u003e member and modifying the systick timer only if the ENABLE bit of\n\u003e the SYSTICK Control and Status Register is set.\n\u003e\n\u003e In addition, the Cortex-M3 Devices Generic User Guide mentioned that\n\u003e \"When ENABLE is set to 1, the counter loads the RELOAD value from the\n\u003e SYST RVR register and then counts down\". This patch adheres to the\n\u003e statement of the user guide.\n\nThis fix is simply incorrect -- or at the least, incomplete:\na write to SYST_CVR must clear any cached state\nsuch that a subsequent write to SYST_CSR.ENABLE will correctly cause\na reload.  Here is a diff that solves the problem without re-introducing\nthe behavior that the original fix was trying to correct:\n\n```diff\ndiff --git a/hw/timer/armv7m_systick.c b/hw/timer/armv7m_systick.c\nindex 74c58bcf24..3f7b267c2d 100644\n--- a/hw/timer/armv7m_systick.c\n+++ b/hw/timer/armv7m_systick.c\n@@ -181,6 +181,15 @@ static MemTxResult systick_write(void *opaque, hwaddr addr,\n         break;\n     case 0x8: /* SysTick Current Value.  Writes reload the timer.  */\n         systick_reload(s, 1);\n+\n+        if ((s-\u003econtrol \u0026 SYSTICK_ENABLE) == 0) {\n+            /*\n+             * If we're not enabled, explicitly clear our tick value to\n+             * assure that when we do become enabled, we correctly reload.\n+             */\n+            s-\u003etick = 0;\n+        }\n+\n         s-\u003econtrol \u0026= ~SYSTICK_COUNTFLAG;\n         break;\n     default:\n```\n\n## Building\n\nAssuming that one has the Rust toolchain for the `thumbv7em-none-eabi` target\ninstalled, it should build with `cargo build`.  For details on installing\nRust (and this tool chain), consult the (excellent) \u003ca\nhref=\"https://rust-embedded.github.io/book/\"\u003eEmbedded Rust Book\u003c/a\u003e -- \nand in particular its \u003ca\nhref=\"https://rust-embedded.github.io/book/start/qemu.html\"\u003echapter on\nQEMU\u003c/a\u003e.\n\n## Running under QEMU\n\nYou can run it with `cargo run`, or, via the command line:\n\n```\nqemu-system-arm -cpu cortex-m3 \\\n\t-machine lm3s6965evb -nographic \\\n\t-semihosting-config enable=on,target=native \\\n\t-kernel ./target/thumbv7m-none-eabi/debug/qemu-systick-bug\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Fqemu-systick-bug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foxidecomputer%2Fqemu-systick-bug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Fqemu-systick-bug/lists"}