| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ot_radio.h" |
| |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/hw/reg.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/driver-unit-test/utils.h> |
| #include <lib/fidl/llcpp/server.h> |
| #include <lib/fit/defer.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <zircon/compiler.h> |
| #include <zircon/status.h> |
| #include <zircon/time.h> |
| |
| #include <iterator> |
| |
| #include <ddktl/fidl.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_counted.h> |
| #include <fbl/ref_ptr.h> |
| |
| #include "src/connectivity/openthread/drivers/ot-radio/ot_radio_bind.h" |
| #include "src/connectivity/openthread/drivers/ot-radio/ot_radio_bootloader.h" |
| |
| namespace ot { |
| namespace lowpan_spinel_fidl = fuchsia_lowpan_spinel; |
| |
| OtRadioDevice::LowpanSpinelDeviceFidlImpl::LowpanSpinelDeviceFidlImpl(OtRadioDevice& ot_radio) |
| : ot_radio_obj_(ot_radio) {} |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::Bind( |
| async_dispatcher_t* dispatcher, fidl::ServerEnd<lowpan_spinel_fidl::Device> channel) { |
| fidl::OnUnboundFn<LowpanSpinelDeviceFidlImpl> on_unbound = |
| [](LowpanSpinelDeviceFidlImpl* server, fidl::UnbindInfo /*unused*/, |
| fidl::ServerEnd<lowpan_spinel_fidl::Device> /*unused*/) { |
| server->ot_radio_obj_.fidl_impl_obj_.release(); |
| }; |
| ot_radio_obj_.fidl_binding_ = |
| fidl::BindServer(dispatcher, std::move(channel), this, std::move(on_unbound)); |
| } |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::Open(OpenCompleter::Sync& completer) { |
| zx_status_t res = ot_radio_obj_.Reset(); |
| if (res == ZX_OK) { |
| zxlogf(DEBUG, "open succeed, returning"); |
| ot_radio_obj_.power_status_ = OT_SPINEL_DEVICE_ON; |
| (*ot_radio_obj_.fidl_binding_)->OnReadyForSendFrames(kOutboundAllowanceInit); |
| ot_radio_obj_.inbound_allowance_ = 0; |
| ot_radio_obj_.outbound_allowance_ = kOutboundAllowanceInit; |
| ot_radio_obj_.inbound_cnt_ = 0; |
| ot_radio_obj_.outbound_cnt_ = 0; |
| completer.ReplySuccess(); |
| } else { |
| zxlogf(ERROR, "Error in handling FIDL close req: %s, power status: %u", |
| zx_status_get_string(res), ot_radio_obj_.power_status_); |
| completer.ReplyError(lowpan_spinel_fidl::wire::Error::UNSPECIFIED); |
| } |
| } |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::Close(CloseCompleter::Sync& completer) { |
| zx_status_t res = ot_radio_obj_.AssertResetPin(); |
| if (res == ZX_OK) { |
| ot_radio_obj_.power_status_ = OT_SPINEL_DEVICE_OFF; |
| completer.ReplySuccess(); |
| } else { |
| zxlogf(ERROR, "Error in handling FIDL close req: %s, power status: %u", |
| zx_status_get_string(res), ot_radio_obj_.power_status_); |
| completer.ReplyError(lowpan_spinel_fidl::wire::Error::UNSPECIFIED); |
| } |
| } |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::GetMaxFrameSize( |
| GetMaxFrameSizeCompleter::Sync& completer) { |
| completer.Reply(kMaxFrameSize); |
| } |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::SendFrame(::fidl::VectorView<uint8_t> data, |
| SendFrameCompleter::Sync& completer) { |
| if (ot_radio_obj_.power_status_ == OT_SPINEL_DEVICE_OFF) { |
| (*ot_radio_obj_.fidl_binding_)->OnError(lowpan_spinel_fidl::wire::Error::CLOSED, false); |
| } else if (data.count() > kMaxFrameSize) { |
| (*ot_radio_obj_.fidl_binding_) |
| ->OnError(lowpan_spinel_fidl::wire::Error::OUTBOUND_FRAME_TOO_LARGE, false); |
| } else if (ot_radio_obj_.outbound_allowance_ == 0) { |
| // Client violates the protocol, close FIDL channel and device. Will not send OnError event. |
| ot_radio_obj_.power_status_ = OT_SPINEL_DEVICE_OFF; |
| ot_radio_obj_.AssertResetPin(); |
| ot_radio_obj_.fidl_binding_->Close(ZX_ERR_IO_OVERRUN); |
| completer.Close(ZX_ERR_IO_OVERRUN); |
| } else { |
| // All good, send out the frame. |
| zx_status_t res = ot_radio_obj_.RadioPacketTx(data.begin(), data.count()); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "Error in handling send frame req: %s", zx_status_get_string(res)); |
| } else { |
| ot_radio_obj_.outbound_allowance_--; |
| ot_radio_obj_.outbound_cnt_++; |
| zxlogf(DEBUG, "Successfully Txed pkt, total tx pkt %lu", ot_radio_obj_.outbound_cnt_); |
| if ((ot_radio_obj_.outbound_cnt_ & 1) == 0) { |
| (*ot_radio_obj_.fidl_binding_)->OnReadyForSendFrames(kOutboundAllowanceInc); |
| ot_radio_obj_.outbound_allowance_ += kOutboundAllowanceInc; |
| } |
| } |
| } |
| } |
| |
| void OtRadioDevice::LowpanSpinelDeviceFidlImpl::ReadyToReceiveFrames( |
| uint32_t number_of_frames, ReadyToReceiveFramesCompleter::Sync& completer) { |
| zxlogf(DEBUG, "ot-radio: allow to receive %u frame", number_of_frames); |
| ot_radio_obj_.inbound_allowance_ += number_of_frames; |
| if (ot_radio_obj_.inbound_allowance_ > 0 && ot_radio_obj_.spinel_framer_.get()) { |
| ot_radio_obj_.spinel_framer_->SetInboundAllowanceStatus(true); |
| if (ot_radio_obj_.interrupt_is_asserted_) { |
| // signal the event loop thread to handle interrupt |
| ot_radio_obj_.InvokeInterruptHandler(); |
| } |
| ot_radio_obj_.ReadRadioPacket(); |
| } |
| } |
| |
| OtRadioDevice::OtRadioDevice(zx_device_t* device) |
| : ddk::Device<OtRadioDevice, ddk::Unbindable, ddk::Messageable>(device), |
| loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {} |
| |
| zx_status_t OtRadioDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| fidl::WireDispatch<lowpan_spinel_fidl::DeviceSetup>(this, msg, &transaction); |
| return transaction.Status(); |
| } |
| |
| void OtRadioDevice::SetChannel(fidl::ServerEnd<fuchsia_lowpan_spinel::Device> request, |
| SetChannelCompleter::Sync& completer) { |
| if (fidl_impl_obj_ != nullptr) { |
| zxlogf(ERROR, "ot-radio: channel already set"); |
| completer.ReplyError(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| if (!request.is_valid()) { |
| completer.ReplyError(ZX_ERR_BAD_HANDLE); |
| return; |
| } |
| fidl_impl_obj_ = std::make_unique<LowpanSpinelDeviceFidlImpl>(*this); |
| fidl_impl_obj_->Bind(loop_.dispatcher(), std::move(request)); |
| completer.ReplySuccess(); |
| } |
| |
| zx_status_t OtRadioDevice::StartLoopThread() { |
| zxlogf(DEBUG, "Start loop thread"); |
| zx_status_t status = loop_.StartThread("ot-stack-loop"); |
| if (status == ZX_OK) { |
| thrd_status_.loop_thrd_running = true; |
| } |
| return status; |
| } |
| |
| bool OtRadioDevice::RunUnitTests(void* ctx, zx_device_t* parent, zx_handle_t channel) { |
| return driver_unit_test::RunZxTests("OtRadioTests", parent, channel); |
| } |
| |
| zx_status_t OtRadioDevice::Init() { |
| spi_ = ddk::SpiProtocolClient(parent(), "spi"); |
| if (!spi_.is_valid()) { |
| zxlogf(ERROR, "ot-radio %s: failed to acquire spi", __func__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| gpio_[OT_RADIO_INT_PIN] = ddk::GpioProtocolClient(parent(), "gpio-int"); |
| if (!gpio_[OT_RADIO_INT_PIN].is_valid()) { |
| zxlogf(ERROR, "ot-radio %s: failed to acquire interrupt gpio", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| zx_status_t status = gpio_[OT_RADIO_INT_PIN].ConfigIn(GPIO_NO_PULL); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio %s: failed to configure interrupt gpio", __func__); |
| return status; |
| } |
| |
| status = gpio_[OT_RADIO_INT_PIN].GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW, &interrupt_); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio %s: failed to get interrupt", __func__); |
| return status; |
| } |
| |
| gpio_[OT_RADIO_RESET_PIN] = ddk::GpioProtocolClient(parent(), "gpio-reset"); |
| if (!gpio_[OT_RADIO_RESET_PIN].is_valid()) { |
| zxlogf(ERROR, "ot-radio %s: failed to acquire reset gpio", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| status = gpio_[OT_RADIO_RESET_PIN].ConfigOut(1); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio %s: failed to configure rst gpio, status = %d", __func__, status); |
| return status; |
| } |
| |
| gpio_[OT_RADIO_BOOTLOADER_PIN] = ddk::GpioProtocolClient(parent(), "gpio-bootloader"); |
| if (!gpio_[OT_RADIO_BOOTLOADER_PIN].is_valid()) { |
| zxlogf(ERROR, "ot-radio %s: failed to acquire radio bootloader pin", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| status = gpio_[OT_RADIO_BOOTLOADER_PIN].ConfigOut(1); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio %s: failed to configure bootloader gpio, status = %d", __func__, |
| status); |
| return status; |
| } |
| |
| zx_device_t* pdev_fragment = nullptr; |
| bool found = |
| device_get_fragment(parent(), "fuchsia.hardware.platform.device.PDev", &pdev_fragment); |
| if (!found) { |
| zxlogf(ERROR, "ot-radio %s: failed to acquire pdev fragment", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| size_t actual; |
| uint32_t device_id; |
| status = device_get_metadata(pdev_fragment, DEVICE_METADATA_PRIVATE, &device_id, |
| sizeof(device_id), &actual); |
| if (status != ZX_OK || sizeof(device_id) != actual) { |
| zxlogf(ERROR, "ot-radio: failed to read metadata"); |
| return status == ZX_OK ? ZX_ERR_INTERNAL : status; |
| } |
| |
| spinel_framer_ = std::make_unique<ot::SpinelFramer>(); |
| spinel_framer_->Init(spi_); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t OtRadioDevice::ReadRadioPacket() { |
| if ((inbound_allowance_ > 0) && (spinel_framer_->IsPacketPresent())) { |
| spinel_framer_->ReceivePacketFromRadio(spi_rx_buffer_, &spi_rx_buffer_len_); |
| if (spi_rx_buffer_len_ > 0) { |
| if (thrd_status_.loop_thrd_running) { |
| async::PostTask(loop_.dispatcher(), [this, pkt = std::move(spi_rx_buffer_), |
| len = std::move(spi_rx_buffer_len_)]() { |
| this->HandleRadioRxFrame(pkt, len); |
| }); |
| } else { |
| // Loop thread is not running, this is one off case to be handled |
| // Results either when running test or getting NCP version |
| inbound_allowance_ = 0; |
| spinel_framer_->SetInboundAllowanceStatus(false); |
| } |
| |
| // Signal to driver test, waiting for a response |
| sync_completion_signal(&spi_rx_complete_); |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t OtRadioDevice::HandleRadioRxFrame(uint8_t* frameBuffer, uint16_t length) { |
| zxlogf(DEBUG, "ot-radio: received frame of len:%d", length); |
| if (power_status_ == OT_SPINEL_DEVICE_ON) { |
| auto data = fidl::VectorView<uint8_t>::FromExternal(frameBuffer, length); |
| zx_status_t res = (*fidl_binding_)->OnReceiveFrame(std::move(data)); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: failed to send OnReceive() event due to %s", |
| zx_status_get_string(res)); |
| } |
| inbound_allowance_--; |
| inbound_cnt_++; |
| if ((inbound_allowance_ == 0) && spinel_framer_.get()) { |
| spinel_framer_->SetInboundAllowanceStatus(false); |
| } |
| } else { |
| zxlogf(ERROR, "OtRadioDevice::HandleRadioRxFrame(): Radio is off"); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t OtRadioDevice::RadioPacketTx(uint8_t* frameBuffer, uint16_t length) { |
| zxlogf(DEBUG, "ot-radio: RadioPacketTx"); |
| zx_port_packet packet = {PORT_KEY_TX_TO_RADIO, ZX_PKT_TYPE_USER, ZX_OK, {}}; |
| if (!port_.is_valid()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| memcpy(spi_tx_buffer_, frameBuffer, length); |
| spi_tx_buffer_len_ = length; |
| return port_.queue(&packet); |
| } |
| |
| zx_status_t OtRadioDevice::InvokeInterruptHandler() { |
| zxlogf(DEBUG, "ot-radio: InvokeInterruptHandler"); |
| zx_port_packet packet = {PORT_KEY_RADIO_IRQ, ZX_PKT_TYPE_USER, ZX_OK, {}}; |
| if (!port_.is_valid()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| return port_.queue(&packet); |
| } |
| |
| bool OtRadioDevice::IsInterruptAsserted() { |
| uint8_t pin_level = 0; |
| gpio_[OT_RADIO_INT_PIN].Read(&pin_level); |
| return pin_level == 0; |
| } |
| |
| zx_status_t OtRadioDevice::DriverUnitTestGetNCPVersion() { return GetNCPVersion(); } |
| |
| zx_status_t OtRadioDevice::GetNCPVersion() { |
| spinel_framer_->SetInboundAllowanceStatus(true); |
| inbound_allowance_ = kOutboundAllowanceInit; |
| uint8_t get_ncp_version_cmd[] = {0x80, 0x02, 0x02}; // HEADER, CMD ID, PROPERTY ID |
| // populate TID (lower 4 bits in header) |
| get_ncp_version_cmd[0] = (get_ncp_version_cmd[0] & 0xf0) | (kGetNcpVersionTID & 0x0f); |
| return RadioPacketTx(get_ncp_version_cmd, sizeof(get_ncp_version_cmd)); |
| } |
| |
| zx_status_t OtRadioDevice::DriverUnitTestGetResetEvent() { |
| spinel_framer_->SetInboundAllowanceStatus(true); |
| inbound_allowance_ = kOutboundAllowanceInit; |
| return Reset(); |
| } |
| |
| zx_status_t OtRadioDevice::AssertResetPin() { |
| zx_status_t status = ZX_OK; |
| zxlogf(DEBUG, "ot-radio: assert reset pin"); |
| |
| status = gpio_[OT_RADIO_RESET_PIN].Write(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: gpio write failed"); |
| return status; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| return status; |
| } |
| |
| zx_status_t OtRadioDevice::Reset() { |
| zx_status_t status = ZX_OK; |
| zxlogf(DEBUG, "ot-radio: reset"); |
| |
| status = gpio_[OT_RADIO_RESET_PIN].Write(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: gpio write failed"); |
| return status; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| |
| status = gpio_[OT_RADIO_RESET_PIN].Write(1); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: gpio write failed"); |
| return status; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(400))); |
| return status; |
| } |
| |
| zx_status_t OtRadioDevice::RadioThread() { |
| zx_status_t status = ZX_OK; |
| zxlogf(INFO, "ot-radio: entered thread"); |
| |
| while (true) { |
| zx_port_packet_t packet = {}; |
| int timeout_ms = spinel_framer_->GetTimeoutMs(); |
| auto status = port_.wait(zx::deadline_after(zx::msec(timeout_ms)), &packet); |
| |
| if (status == ZX_ERR_TIMED_OUT) { |
| spinel_framer_->TrySpiTransaction(); |
| ReadRadioPacket(); |
| continue; |
| } else if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: port wait failed: %d", status); |
| return thrd_error; |
| } |
| |
| if (packet.key == PORT_KEY_EXIT_THREAD) { |
| break; |
| } else if (packet.key == PORT_KEY_RADIO_IRQ) { |
| interrupt_.ack(); |
| zxlogf(DEBUG, "ot-radio: interrupt"); |
| do { |
| spinel_framer_->HandleInterrupt(); |
| ReadRadioPacket(); |
| |
| interrupt_is_asserted_ = IsInterruptAsserted(); |
| } while (interrupt_is_asserted_ && inbound_allowance_ != 0); |
| } else if (packet.key == PORT_KEY_TX_TO_RADIO) { |
| spinel_framer_->SendPacketToRadio(spi_tx_buffer_, spi_tx_buffer_len_); |
| } |
| } |
| zxlogf(DEBUG, "ot-radio: exiting"); |
| |
| return status; |
| } |
| |
| zx_status_t OtRadioDevice::CreateBindAndStart(void* ctx, zx_device_t* parent) { |
| std::unique_ptr<OtRadioDevice> ot_radio_dev; |
| zx_status_t status = Create(ctx, parent, &ot_radio_dev); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = ot_radio_dev->Bind(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // device intentionally leaked as it is now held by DevMgr |
| auto dev_ptr = ot_radio_dev.release(); |
| |
| status = dev_ptr->Start(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t OtRadioDevice::Create(void* ctx, zx_device_t* parent, |
| std::unique_ptr<OtRadioDevice>* out) { |
| auto dev = std::make_unique<OtRadioDevice>(parent); |
| zx_status_t status = dev->Init(); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: Driver init failed %d", status); |
| return status; |
| } |
| |
| *out = std::move(dev); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t OtRadioDevice::Bind() { |
| zx_status_t status = DdkAdd(ddk::DeviceAddArgs("ot-radio").set_proto_id(ZX_PROTOCOL_OT_RADIO)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: Could not create device: %d", status); |
| return status; |
| } else { |
| zxlogf(DEBUG, "ot-radio: Added device"); |
| } |
| return status; |
| } |
| |
| zx_status_t OtRadioDevice::CreateAndBindPortToIntr() { |
| zx_status_t status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: port create failed %d", status); |
| return status; |
| } |
| |
| status = interrupt_.bind(port_, PORT_KEY_RADIO_IRQ, 0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: interrupt bind failed %d", status); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void OtRadioDevice::StartRadioThread() { |
| auto callback = [](void* cookie) { |
| return reinterpret_cast<OtRadioDevice*>(cookie)->RadioThread(); |
| }; |
| int ret = thrd_create_with_name(&thread_, callback, this, "ot-radio-thread"); |
| ZX_DEBUG_ASSERT(ret == thrd_success); |
| |
| // Set status flag so shutdown can take appropriate action |
| // cleared by StopRadioThread |
| thrd_status_.radio_thrd_running = true; |
| } |
| |
| zx_status_t OtRadioDevice::Start() { |
| zx_status_t status = CreateAndBindPortToIntr(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| StartRadioThread(); |
| auto cleanup = fit::defer([&]() { ShutDown(); }); |
| |
| #ifdef INTERNAL_ACCESS |
| // Update the NCP Firmware if new version is available |
| bool update_fw = false; |
| status = CheckFWUpdateRequired(&update_fw); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: CheckFWUpdateRequired failed with status: %d", status); |
| return status; |
| } |
| |
| if (update_fw) { |
| // Print as it may be useful, expected to be rare occurrence, |
| zxlogf(INFO, "ot-radio: Will start FW update"); |
| |
| // Stop the loop for handling port events, so port can be used by bootloader |
| StopRadioThread(); |
| |
| // Update firmware here |
| OtRadioDeviceBootloader dev_bl(this); |
| OtRadioBlResult result = dev_bl.UpdateRadioFirmware(); |
| if (result != BL_RET_SUCCESS) { |
| zxlogf(ERROR, "ot-radio: radio firmware update failed with %d. Last zx_status %d", result, |
| dev_bl.GetLastZxStatus()); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| zxlogf(INFO, "ot-radio: FW update done successfully"); |
| |
| // Restart the Radio thread: |
| StartRadioThread(); |
| } else { |
| zxlogf(DEBUG, "ot-radio: NCP firmware is already up-to-date"); |
| } |
| #endif |
| |
| status = StartLoopThread(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: Could not start loop thread"); |
| return status; |
| } |
| |
| zxlogf(DEBUG, "ot-radio: Started thread"); |
| |
| cleanup.cancel(); |
| |
| return status; |
| } |
| |
| #ifdef INTERNAL_ACCESS |
| zx_status_t OtRadioDevice::CheckFWUpdateRequired(bool* update_fw) { |
| *update_fw = false; |
| |
| // Get the new firmware version: |
| std::string new_fw_version = GetNewFirmwareVersion(); |
| if (new_fw_version.size() == 0) { |
| // Invalid version string indicates invalid firmware |
| zxlogf(ERROR, "ot-radio: The new firmware is invalid"); |
| *update_fw = false; |
| // Return error instead of ZX_OK and just not-updating, |
| // may point to some bug |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| int attempts; |
| bool response_received = false; |
| |
| // Now get the ncp version |
| auto status = GetNCPVersion(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ot-radio: get ncp version failed with status: %d", status); |
| return status; |
| } |
| |
| for (attempts = 0; attempts < kGetNcpVersionMaxRetries; attempts++) { |
| zxlogf(DEBUG, "ot-radio: waiting for response for GetNCPVersion cmd, attempt: %d / %d", |
| attempts + 1, kGetNcpVersionMaxRetries); |
| |
| // Simply update the allowance for each attempt. Radio will send |
| // response to GetNCPVersion to us eventually. Ignore any response to |
| // earlier commands. |
| spinel_framer_->SetInboundAllowanceStatus(true); |
| inbound_allowance_ = kOutboundAllowanceInit; |
| |
| // Wait for response to arrive, signaled by spi_rx_complete_ |
| status = sync_completion_wait(&spi_rx_complete_, ZX_SEC(10)); |
| sync_completion_reset(&spi_rx_complete_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, |
| "ot-radio: sync_completion_wait failed with status: %d, \ |
| this means firmware may be behaving incorrectly\n", |
| status); |
| // We want to update fw in this case |
| *update_fw = true; |
| return ZX_OK; |
| } |
| |
| // Check for matching TID in spinel header |
| if ((spi_rx_buffer_[0] & 0xf) == kGetNcpVersionTID) { |
| response_received = true; |
| break; |
| } |
| } |
| |
| if (!response_received) { |
| zxlogf(ERROR, "ot-radio: no matching response is received for get ncp version command."); |
| zxlogf(ERROR, "ot-radio: updating the the firmware"); |
| // This can again mean bad firmware, so update the firmware: |
| *update_fw = true; |
| return ZX_OK; |
| } |
| |
| // Response is received, copy it to the string cur_fw_version |
| // First make sure that last character is null |
| ZX_DEBUG_ASSERT(spi_rx_buffer_len_ <= kMaxFrameSize); |
| spi_rx_buffer_[spi_rx_buffer_len_ - 1] = '\0'; |
| zxlogf(DEBUG, "ot-radio: response received size = %d, value : %s", spi_rx_buffer_len_, |
| reinterpret_cast<char*>(&(spi_rx_buffer_[3]))); |
| std::string cur_fw_version; |
| cur_fw_version.assign(reinterpret_cast<char*>(&(spi_rx_buffer_[3]))); |
| |
| // We want to update firmware if the versions don't match |
| *update_fw = (cur_fw_version.compare(new_fw_version) != 0); |
| if (*update_fw) { |
| zxlogf(INFO, "ot-radio: cur_fw_version: %s, new_fw_version: %s", cur_fw_version.c_str(), |
| new_fw_version.c_str()); |
| } |
| return ZX_OK; |
| } |
| #endif |
| |
| void OtRadioDevice::DdkRelease() { delete this; } |
| |
| void OtRadioDevice::DdkUnbind(ddk::UnbindTxn txn) { |
| ShutDown(); |
| txn.Reply(); |
| } |
| |
| void OtRadioDevice::StopRadioThread() { |
| if (thrd_status_.radio_thrd_running) { |
| zx_port_packet packet = {PORT_KEY_EXIT_THREAD, ZX_PKT_TYPE_USER, ZX_OK, {}}; |
| port_.queue(&packet); |
| thrd_join(thread_, NULL); |
| thrd_status_.radio_thrd_running = false; |
| } |
| } |
| |
| void OtRadioDevice::StopLoopThread() { |
| if (thrd_status_.loop_thrd_running) { |
| loop_.Shutdown(); |
| thrd_status_.loop_thrd_running = false; |
| } |
| } |
| |
| zx_status_t OtRadioDevice::ShutDown() { |
| StopRadioThread(); |
| |
| gpio_[OT_RADIO_INT_PIN].ReleaseInterrupt(); |
| interrupt_.destroy(); |
| |
| StopLoopThread(); |
| |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = OtRadioDevice::CreateBindAndStart; |
| ops.run_unit_tests = OtRadioDevice::RunUnitTests; |
| return ops; |
| }(); |
| |
| } // namespace ot |
| |
| ZIRCON_DRIVER(ot, ot::driver_ops, "ot_radio", "0.1"); |