// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_system/device_handler.h"

#include "hardware/watchdog.h"
#include "pw_cpu_exception/state.h"
#include "pw_cpu_exception_cortex_m/snapshot.h"
#include "pw_thread_freertos/snapshot.h"

namespace pw::system::device_handler {

namespace {

// These symbols are added to the default pico_sdk linker script as part
// of the build process. If the build fails due to missing these symbols,
// it may be because a different linker script is configured and these
// symbols need added.
extern "C" uint32_t __pw_code_begin;
extern "C" uint32_t __pw_code_end;

extern "C" uint32_t __StackBottom;
extern "C" uint32_t __StackTop;

uintptr_t GetLinkerSymbolValue(const uint32_t& symbol) {
  return reinterpret_cast<uintptr_t>(&symbol);
}

bool IsAddressExecutable(uintptr_t address) {
  const uintptr_t code_begin = GetLinkerSymbolValue(__pw_code_begin);
  const uintptr_t code_end = GetLinkerSymbolValue(__pw_code_end);

  if ((address >= code_begin) && (address <= code_end)) {
    return true;
  }

  return false;
}

Status AddressFilteredDumper(
    thread::proto::pwpb::Thread::StreamEncoder& encoder, ConstByteSpan stack) {
  span<const uint32_t> addresses =
      span<const uint32_t>(reinterpret_cast<const uint32_t*>(stack.data()),
                           stack.size_bytes() / sizeof(uint32_t));
  for (const uint32_t address : addresses) {
    if (IsAddressExecutable(address)) {
      auto status = encoder.WriteRawBacktrace(address);
      if (status != OkStatus())
        return status;
    }
  }
  return OkStatus();
}

}  // namespace

void RebootSystem() { watchdog_reboot(0, 0, 0); }

void CapturePlatformMetadata(
    snapshot::pwpb::Metadata::StreamEncoder& metadata_encoder) {
// The device_handler is shared between rp2040 and 2350, so handle differences
// with the preprocessor.
#if _PW_ARCH_ARM_V6M
  // TODO: https://pwbug.dev/357132837 - Review if IgnoreError is correct here.
  metadata_encoder.WriteCpuArch(snapshot::pwpb::CpuArchitecture::Enum::ARMV6M)
      .IgnoreError();
#elif _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
  // TODO: https://pwbug.dev/357132837 - Review if IgnoreError is correct here.
  metadata_encoder.WriteCpuArch(snapshot::pwpb::CpuArchitecture::Enum::ARMV8M)
      .IgnoreError();
#else
#error "Unknown CPU architecture."
#endif  // _PW_ARCH_ARM_V6M
}

Status CaptureCpuState(
    const pw_cpu_exception_State& cpu_state,
    snapshot::pwpb::Snapshot::StreamEncoder& snapshot_encoder) {
  return cpu_exception::cortex_m::SnapshotCpuState(
      cpu_state,
      *static_cast<cpu_exception::cortex_m::pwpb::SnapshotCpuStateOverlay::
                       StreamEncoder*>(
          static_cast<protobuf::StreamEncoder*>(&snapshot_encoder)));
}

Status CaptureMainStackThread(
    const pw_cpu_exception_State& cpu_state,
    thread::proto::pwpb::SnapshotThreadInfo::StreamEncoder& encoder) {
  uintptr_t stack_low_addr = GetLinkerSymbolValue(__StackBottom);
  uintptr_t stack_high_addr = GetLinkerSymbolValue(__StackTop);
  thread::ProcessThreadStackCallback stack_dumper =
      device_handler::AddressFilteredDumper;

  return cpu_exception::cortex_m::SnapshotMainStackThread(
      cpu_state, stack_low_addr, stack_high_addr, encoder, stack_dumper);
}

Status CaptureThreads(
    uint32_t running_thread_stack_pointer,
    thread::proto::pwpb::SnapshotThreadInfo::StreamEncoder& encoder) {
  thread::ProcessThreadStackCallback stack_dumper =
      device_handler::AddressFilteredDumper;
  return thread::freertos::SnapshotThreads(
      reinterpret_cast<void*>(running_thread_stack_pointer),
      encoder,
      stack_dumper);
}

}  // namespace pw::system::device_handler
