| // Copyright 2020 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/boot-options/boot-options.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <platform.h> |
| #include <reg.h> |
| #include <string.h> |
| #include <zircon/boot/driver-config.h> |
| #include <zircon/types.h> |
| |
| #include <arch/arm64/periphmap.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/spinlock.h> |
| #include <kernel/timer.h> |
| #include <pdev/driver.h> |
| #include <pdev/watchdog.h> |
| #include <vm/physmap.h> |
| |
| class GenericWatchdog32 { |
| public: |
| constexpr GenericWatchdog32() {} |
| |
| // TODO(johngro) : for now, don't actually declare a destructor. We don't |
| // want to end up registering a global .dtor for no reason. If/when we get to |
| // the point where the kernel can actually "exit" for sanitizer analysis, we |
| // will want to come back here and enable this. |
| //~GenericWatchdog32() { pet_timer_.Cancel(); } |
| |
| // Early init takes place while we are still single threaded, and don't need |
| // to worry about thread safety. |
| void InitEarly(const void* driver_data, uint32_t length); |
| void Init(const void*, uint32_t); |
| |
| // Actions |
| void Pet() TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| PetLocked(); |
| } |
| |
| zx_status_t SetEnabled(bool enb) TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| |
| // Nothing to do if we are already in the desired state. |
| if (enb == is_enabled_) { |
| return ZX_OK; |
| } |
| |
| if (!(enb ? cfg_.enable_action.addr : cfg_.disable_action.addr)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| is_enabled_ = enb; |
| |
| if (is_enabled_) { |
| // Enable the timer, then immediately pet the watchdog, and set up the pet |
| // timer. |
| TakeAction(cfg_.enable_action); |
| HandlePetTimer(); |
| } else { |
| // Disable the watchdog and cancel any in-flight timer. |
| TakeAction(cfg_.disable_action); |
| pet_timer_.Cancel(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Accessors |
| zx_duration_t timeout_nsec() const { return cfg_.watchdog_period_nsec; } |
| bool is_enabled() const TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| return is_enabled_; |
| } |
| |
| zx_time_t last_pet_time() const TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| return last_pet_time_; |
| } |
| |
| void SuppressPetting(bool suppress) TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| is_petting_suppressed_ = suppress; |
| } |
| |
| bool IsPettingSuppressed() const TA_EXCL(lock_) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| return is_petting_suppressed_; |
| } |
| |
| private: |
| static bool TranslatePAddr(uint64_t* paddr); |
| void TakeAction(const dcfg_generic_32bit_watchdog_action_t& action) TA_REQ(lock_) { |
| uint32_t val = readl(action.addr); |
| val &= ~action.clr_mask; |
| val |= action.set_mask; |
| writel(val, action.addr); |
| } |
| |
| zx_time_t PetLocked() TA_REQ(lock_) { |
| // Even if petting is suppressed, take a look at the time just before the |
| // pet was supposed to happen. This is the value we will use when computing |
| // the next pet timer, instead of basing it on the last_pet_time_. This is |
| // important because we want the last_pet_time_ to reflect the last time we |
| // _actually_ pet the dog, but if we use it to schedule our next timer |
| // deadline, we might end up scheduling our timers in the past, causing our |
| // core to get stuck in its timer handler. |
| zx_time_t now = current_time(); |
| if (!is_petting_suppressed_) { |
| last_pet_time_ = now; |
| TakeAction(cfg_.pet_action); |
| } |
| return now; |
| } |
| void HandlePetTimer() TA_REQ(lock_); |
| void PretendLocked() TA_ASSERT(lock_) {} |
| |
| mutable DECLARE_SPINLOCK(GenericWatchdog32) lock_; |
| dcfg_generic_32bit_watchdog_t cfg_{}; |
| zx_status_t early_init_result_ = ZX_ERR_INTERNAL; |
| zx_time_t last_pet_time_ TA_GUARDED(lock_) = 0; |
| Timer pet_timer_ TA_GUARDED(lock_); |
| bool is_enabled_ TA_GUARDED(lock_) = false; |
| bool is_petting_suppressed_ TA_GUARDED(lock_) = false; |
| }; |
| |
| static GenericWatchdog32 g_watchdog; |
| |
| static const pdev_watchdog_ops_t THUNKS = { |
| .pet = []() { g_watchdog.Pet(); }, |
| .set_enabled = [](bool enb) { return g_watchdog.SetEnabled(enb); }, |
| .is_enabled = []() { return g_watchdog.is_enabled(); }, |
| .get_timeout_nsec = []() { return g_watchdog.timeout_nsec(); }, |
| .get_last_pet_time = []() { return g_watchdog.last_pet_time(); }, |
| .suppress_petting = [](bool suppress) { g_watchdog.SuppressPetting(suppress); }, |
| .is_petting_suppressed = []() -> bool { return g_watchdog.IsPettingSuppressed(); }, |
| }; |
| |
| void GenericWatchdog32::InitEarly(const void* driver_data, uint32_t length) { |
| // "Assert" that we are holding the lock. While this is technically a no-op, |
| // it tells the thread analyzer to pretend that we are holding the lock. We |
| // are in the early init stage of boot, so it is too early to need to worry |
| // about multi-thread safety issues, but we don't want to actually be |
| // obtaining and releasing the spin lock at this point. Along with the |
| // annotations in the class, pretending that we are holding the lock at this |
| // point in time will make certain that we are following all of the locking |
| // rules. |
| PretendLocked(); |
| |
| // Sanity check our config first. If they are invalid, we cannot proceed |
| // (and if the watchdog is already enable, we are gonna end up rebooting). |
| // |
| // Sadly, it is too early to do any logging. If we manage to make it to the |
| // PLATFORM init level, we will log the errors there. |
| |
| // We must have config, and that config must be of the proper length. If that |
| // checks out, go ahead and copy the config. |
| if ((driver_data == nullptr) || (length != sizeof(cfg_))) { |
| early_init_result_ = ZX_ERR_INVALID_ARGS; |
| return; |
| } |
| memcpy(&cfg_, driver_data, sizeof(cfg_)); |
| |
| // All generic watchdog drivers must have some way of petting the dog. |
| // Enable/disable is optional, but not petting. |
| if (!cfg_.pet_action.addr) { |
| early_init_result_ = ZX_ERR_INVALID_ARGS; |
| return; |
| } |
| |
| // The watchdog period must be at least 1 mSec. We don't want to spend 15% of |
| // our CPU petting the watchdog all of the time. |
| if (cfg_.watchdog_period_nsec < KDRV_GENERIC_32BIT_WATCHDOG_MIN_PERIOD) { |
| early_init_result_ = ZX_ERR_INVALID_ARGS; |
| return; |
| } |
| |
| // Great! Things look good. Translate the physical addresses for the various |
| // actions to virtual addresses. If we cannot translate the pet address, we |
| // have a problem. If we cannot translate the enable or disable address, then |
| // so be it. That functionality will be unavailable, but at least we can pet |
| // the dog. |
| if (!TranslatePAddr(&cfg_.pet_action.addr)) { |
| early_init_result_ = ZX_ERR_IO; |
| } |
| |
| TranslatePAddr(&cfg_.enable_action.addr); |
| TranslatePAddr(&cfg_.disable_action.addr); |
| |
| // Record our initial enabled/disabled state. |
| is_enabled_ = (cfg_.flags & KDRV_GENERIC_32BIT_WATCHDOG_FLAG_ENABLED) != 0; |
| |
| // If we are currently enabled, be sure to pet the dog ASAP. We don't want it |
| // to fire while we are bringing up the kernel to the point where we can do |
| // stuff like set timers. In addition, if the cmd-line flag was passed to |
| // force disable the watchdog, do so if possible just after we have pet it. |
| PetLocked(); |
| if (gBootOptions->force_watchdog_disabled && is_enabled_ && cfg_.disable_action.addr) { |
| TakeAction(cfg_.disable_action); |
| is_enabled_ = false; |
| } |
| |
| // Register our driver. Note that the pdev layer is going to hold onto our |
| // thunk table, not make a copy. We need to be sure that we don't let our |
| // table go out of scope (which is why it is file local). |
| pdev_register_watchdog(&THUNKS); |
| |
| // Things went well. Make sure the later init stage knows that. |
| early_init_result_ = ZX_OK; |
| } |
| |
| void GenericWatchdog32::Init(const void*, uint32_t) { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| |
| // Ok, we are much farther along in the boot now. We should be able to do |
| // things like report errors and set timers at this point. Start by checking |
| // out how things went during early init. If things went poorly, try to log |
| // why. Hopefully the watchdog is currently disabled, or we are going to |
| // reboot Real Soon Now(tm). |
| if (early_init_result_ != ZX_OK) { |
| dprintf( |
| INFO, |
| "WDT: Generic watchdog driver attempted to load, but failed during early init (res %d).\n", |
| early_init_result_); |
| return; |
| } |
| |
| // Report that the driver has successfully loaded, along with some handy info |
| // about the hardware state. |
| dprintf(INFO, "WDT: Generic watchdog driver loaded. Period (%ld.%03ld mSec) Enabled (%s)\n", |
| timeout_nsec() / ZX_MSEC(1), (timeout_nsec() % ZX_MSEC(1)) / ZX_USEC(1), |
| is_enabled_ ? "yes" : "no"); |
| |
| // If the force disable cmd line flag was passed, report that here. |
| if (gBootOptions->force_watchdog_disabled) { |
| if (cfg_.disable_action.addr) { |
| dprintf(INFO, "WDT: %s was set, watchdog was force-disabled\n", |
| kForceWatchdogDisabledName.data()); |
| } else { |
| dprintf(INFO, |
| "WDT: %s was set, but the watchdog cannot be disabled. It is " |
| "currently %s.\n", |
| kForceWatchdogDisabledName.data(), is_enabled_ ? "enabled" : "disabled"); |
| } |
| } |
| |
| // If we are enabled, pet the dog now and set our pet timer. |
| HandlePetTimer(); |
| } |
| |
| void GenericWatchdog32::HandlePetTimer() { |
| if (is_enabled_) { |
| zx_time_t next_pet_time = zx_time_add_duration(PetLocked(), timeout_nsec() / 2); |
| Deadline next_pet_deadline{next_pet_time, {(timeout_nsec() / 4), TIMER_SLACK_EARLY}}; |
| pet_timer_.Set( |
| next_pet_deadline, |
| [](Timer*, zx_time_t now, void* arg) { |
| auto thiz = reinterpret_cast<GenericWatchdog32*>(arg); |
| Guard<SpinLock, IrqSave> guard{&thiz->lock_}; |
| thiz->HandlePetTimer(); |
| }, |
| this); |
| } |
| } |
| |
| bool GenericWatchdog32::TranslatePAddr(uint64_t* paddr) { |
| // Translate a register's physical address to a virtual address so we can read |
| // and write it. If we cannot, for some reason, return an error. If the |
| // address is already nullptr, just leave it that way. This is not an error, |
| // it just means that the register for this action is not available. |
| if (*paddr == 0) { |
| return true; |
| } |
| |
| *paddr = periph_paddr_to_vaddr(static_cast<paddr_t>(*paddr)); |
| |
| return (*paddr != 0); |
| } |
| |
| LK_PDEV_INIT( |
| generic_32bit_watchdog_init_early, KDRV_GENERIC_32BIT_WATCHDOG, |
| [](const void* driver_data, uint32_t length) { g_watchdog.InitEarly(driver_data, length); }, |
| LK_INIT_LEVEL_PLATFORM_EARLY) |
| LK_PDEV_INIT( |
| generic_32bit_watchdog_init, KDRV_GENERIC_32BIT_WATCHDOG, |
| [](const void* driver_data, uint32_t length) { g_watchdog.Init(driver_data, length); }, |
| LK_INIT_LEVEL_PLATFORM) |