1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <zircon/assert.h> 6#include <fbl/algorithm.h> 7#include <fbl/auto_lock.h> 8#include <fbl/limits.h> 9#include <stdio.h> 10 11#include <intel-hda/utils/codec-commands.h> 12 13#include "debug-logging.h" 14#include "intel-hda-codec.h" 15#include "intel-hda-controller.h" 16#include "intel-hda-stream.h" 17#include "utils.h" 18 19namespace audio { 20namespace intel_hda { 21 22constexpr size_t IntelHDACodec::PROP_PROTOCOL; 23constexpr size_t IntelHDACodec::PROP_VID; 24constexpr size_t IntelHDACodec::PROP_DID; 25constexpr size_t IntelHDACodec::PROP_MAJOR_REV; 26constexpr size_t IntelHDACodec::PROP_MINOR_REV; 27constexpr size_t IntelHDACodec::PROP_VENDOR_REV; 28constexpr size_t IntelHDACodec::PROP_VENDOR_STEP; 29constexpr size_t IntelHDACodec::PROP_COUNT; 30 31#define SET_DEVICE_PROP(_prop, _value) do { \ 32 static_assert(PROP_##_prop < countof(dev_props_), "Invalid Device Property ID"); \ 33 dev_props_[PROP_##_prop].id = BIND_IHDA_CODEC_##_prop; \ 34 dev_props_[PROP_##_prop].value = (_value); \ 35} while (false) 36 37IntelHDACodec::ProbeCommandListEntry IntelHDACodec::PROBE_COMMANDS[] = { 38 { .verb = GET_PARAM(CodecParam::VENDOR_ID), .parse = &IntelHDACodec::ParseVidDid }, 39 { .verb = GET_PARAM(CodecParam::REVISION_ID), .parse = &IntelHDACodec::ParseRevisionId }, 40}; 41 42#define DEV (static_cast<IntelHDACodec*>(ctx)) 43zx_protocol_device_t IntelHDACodec::CODEC_DEVICE_THUNKS = { 44 .version = DEVICE_OPS_VERSION, 45 .get_protocol = nullptr, 46 .open = nullptr, 47 .open_at = nullptr, 48 .close = nullptr, 49 .unbind = nullptr, 50 .release = nullptr, 51 .read = nullptr, 52 .write = nullptr, 53 .get_size = nullptr, 54 .ioctl = [](void* ctx, 55 uint32_t op, 56 const void* in_buf, 57 size_t in_len, 58 void* out_buf, 59 size_t out_len, 60 size_t* out_actual) -> zx_status_t { 61 return DEV->DeviceIoctl(op, out_buf, out_len, out_actual); 62 }, 63 .suspend = nullptr, 64 .resume = nullptr, 65 .rxrpc = nullptr, 66 .message = nullptr, 67}; 68 69ihda_codec_protocol_ops_t IntelHDACodec::CODEC_PROTO_THUNKS = { 70 .get_driver_channel = [](void* ctx, zx_handle_t* channel_out) -> zx_status_t 71 { 72 ZX_DEBUG_ASSERT(ctx); 73 return DEV->CodecGetDispatcherChannel(channel_out); 74 }, 75}; 76#undef DEV 77 78IntelHDACodec::IntelHDACodec(IntelHDAController& controller, uint8_t codec_id) 79 : controller_(controller), 80 codec_id_(codec_id) { 81 ::memset(&dev_props_, 0, sizeof(dev_props_)); 82 dev_props_[PROP_PROTOCOL].id = BIND_PROTOCOL; 83 dev_props_[PROP_PROTOCOL].value = ZX_PROTOCOL_IHDA_CODEC; 84 default_domain_ = dispatcher::ExecutionDomain::Create(); 85 86 const auto& info = controller_.dev_info(); 87 snprintf(log_prefix_, sizeof(log_prefix_), 88 "IHDA Codec %02x:%02x.%01x/%02x", 89 info.bus_id, info.dev_id, info.func_id, codec_id_); 90} 91 92fbl::RefPtr<IntelHDACodec> IntelHDACodec::Create(IntelHDAController& controller, 93 uint8_t codec_id) { 94 ZX_DEBUG_ASSERT(codec_id < HDA_MAX_CODECS); 95 96 fbl::AllocChecker ac; 97 auto ret = fbl::AdoptRef(new (&ac) IntelHDACodec(controller, codec_id)); 98 if (!ac.check()) { 99 GLOBAL_LOG(ERROR, "Out of memory attempting to allocate codec\n"); 100 return nullptr; 101 } 102 103 if (ret->default_domain_ == nullptr) { 104 LOG_EX(ERROR, *ret, "Out of memory attempting to allocate execution domain\n"); 105 return nullptr; 106 } 107 108 return ret; 109} 110 111zx_status_t IntelHDACodec::Startup() { 112 ZX_DEBUG_ASSERT(state_ == State::PROBING); 113 114 for (size_t i = 0; i < countof(PROBE_COMMANDS); ++i) { 115 CodecCommand cmd(id(), 0u, PROBE_COMMANDS[i].verb); 116 auto job = CodecCmdJobAllocator::New(cmd); 117 118 if (job == nullptr) { 119 LOG(ERROR, "Failed to allocate job during initial codec probe!\n"); 120 return ZX_ERR_NO_MEMORY; 121 } 122 123 zx_status_t res = controller_.QueueCodecCmd(fbl::move(job)); 124 if (res != ZX_OK) { 125 LOG(ERROR, "Failed to queue job (res = %d) during initial codec probe!\n", res); 126 return res; 127 } 128 } 129 130 return ZX_OK; 131} 132 133void IntelHDACodec::SendCORBResponse(const fbl::RefPtr<dispatcher::Channel>& channel, 134 const CodecResponse& resp, 135 uint32_t transaction_id) { 136 ZX_DEBUG_ASSERT(channel != nullptr); 137 ihda_codec_send_corb_cmd_resp_t payload; 138 139 payload.hdr.transaction_id = transaction_id; 140 payload.hdr.cmd = IHDA_CODEC_SEND_CORB_CMD; 141 payload.data = resp.data; 142 payload.data_ex = resp.data_ex; 143 144 zx_status_t res = channel->Write(&payload, sizeof(payload)); 145 if (res != ZX_OK) { 146 LOG(TRACE, "Error writing CORB response (%08x, %08x) res = %d\n", 147 resp.data, resp.data_ex, res); 148 } 149} 150 151void IntelHDACodec::ProcessSolicitedResponse(const CodecResponse& resp, 152 fbl::unique_ptr<CodecCmdJob>&& job) { 153 if (state_ == State::PROBING) { 154 // Are we still in the PROBING stage of things? If so, this job should 155 // have no response channel assigned to it, and we should still be 156 // waiting for responses from the codec to complete the initial probe. 157 ZX_DEBUG_ASSERT(probe_rx_ndx_ < countof(PROBE_COMMANDS)); 158 159 const auto& cmd = PROBE_COMMANDS[probe_rx_ndx_]; 160 161 zx_status_t res = (this->*cmd.parse)(resp); 162 if (res == ZX_OK) { 163 ++probe_rx_ndx_; 164 } else { 165 LOG(ERROR, 166 "Error parsing solicited response during codec probe! (data %08x)\n", 167 resp.data); 168 169 // TODO(johngro) : shutdown and cleanup somehow. 170 state_ = State::FATAL_ERROR; 171 } 172 } else if (job->response_channel() != nullptr) { 173 LOG(SPEW, "Sending solicited response [%08x, %08x] to channel %p\n", 174 resp.data, resp.data_ex, job->response_channel().get()); 175 176 // Does this job have a response channel? If so, attempt to send the 177 // response back on the channel (assuming that it is still open). 178 SendCORBResponse(job->response_channel(), resp, job->transaction_id()); 179 } 180} 181 182void IntelHDACodec::ProcessUnsolicitedResponse(const CodecResponse& resp) { 183 // If we still have a channel to our codec driver, grab a reference to it 184 // and send the unsolicited response to it. 185 fbl::RefPtr<dispatcher::Channel> codec_driver_channel; 186 { 187 fbl::AutoLock codec_driver_channel_lock(&codec_driver_channel_lock_); 188 codec_driver_channel = codec_driver_channel_; 189 } 190 191 if (codec_driver_channel) 192 SendCORBResponse(codec_driver_channel, resp); 193} 194 195void IntelHDACodec::ProcessWakeupEvt() { 196 // TODO(johngro) : handle wakeup events. Wakeup events are delivered for 197 // two reasons. 198 // 199 // 1) The codec had brought the controller out of a low power state for some 200 // reason. 201 // 2) The codec has been hot-unplugged. 202 // 203 // Currently, we support neither power management, nor hot-unplug. Just log 204 // the fact that we have been woken up and do nothing. 205 LOG(WARN, "Wakeup event received - Don't know how to handle this yet!\n"); 206} 207 208void IntelHDACodec::Shutdown() { 209 // Close all existing connections and synchronize with any client threads 210 // who are currently processing requests. 211 state_ = State::SHUTTING_DOWN; 212 default_domain_->Deactivate(); 213 214 // Give any active streams we had back to our controller. 215 IntelHDAStream::Tree streams; 216 { 217 fbl::AutoLock lock(&active_streams_lock_); 218 streams.swap(active_streams_); 219 } 220 221 while (!streams.is_empty()) 222 controller_.ReturnStream(streams.pop_front()); 223 224 state_ = State::SHUT_DOWN; 225} 226 227zx_status_t IntelHDACodec::PublishDevice() { 228 // Generate our name. 229 char name[ZX_DEVICE_NAME_MAX]; 230 snprintf(name, sizeof(name), "intel-hda-codec-%03u", codec_id_); 231 232 // Initialize our device and fill out the protocol hooks 233 device_add_args_t args = {}; 234 args.version = DEVICE_ADD_ARGS_VERSION; 235 args.name = name; 236 args.ctx = this; 237 args.ops = &CODEC_DEVICE_THUNKS; 238 args.proto_id = ZX_PROTOCOL_IHDA_CODEC; 239 args.proto_ops = &CODEC_PROTO_THUNKS; 240 args.props = dev_props_; 241 args.prop_count = countof(dev_props_); 242 243 // Publish the device. 244 zx_status_t res = device_add(controller_.dev_node(), &args, &dev_node_); 245 if (res != ZX_OK) { 246 LOG(ERROR, "Failed to add codec device for \"%s\" (res %d)\n", name, res); 247 return res; 248 } 249 250 return ZX_OK; 251} 252 253zx_status_t IntelHDACodec::ParseVidDid(const CodecResponse& resp) { 254 props_.vid = static_cast<uint16_t>((resp.data >> 16) & 0xFFFF); 255 props_.did = static_cast<uint16_t>(resp.data & 0xFFFF); 256 257 SET_DEVICE_PROP(VID, props_.vid); 258 SET_DEVICE_PROP(DID, props_.did); 259 260 return (props_.vid != 0) ? ZX_OK : ZX_ERR_INTERNAL; 261} 262 263zx_status_t IntelHDACodec::ParseRevisionId(const CodecResponse& resp) { 264 props_.ihda_vmaj = static_cast<uint8_t>((resp.data >> 20) & 0xF); 265 props_.ihda_vmin = static_cast<uint8_t>((resp.data >> 16) & 0xF); 266 props_.rev_id = static_cast<uint8_t>((resp.data >> 8) & 0xFF); 267 props_.step_id = static_cast<uint8_t>(resp.data & 0xFF); 268 269 SET_DEVICE_PROP(MAJOR_REV, props_.ihda_vmaj); 270 SET_DEVICE_PROP(MINOR_REV, props_.ihda_vmin); 271 SET_DEVICE_PROP(VENDOR_REV, props_.rev_id); 272 SET_DEVICE_PROP(VENDOR_STEP, props_.step_id); 273 274 state_ = State::FINDING_DRIVER; 275 return PublishDevice(); 276} 277 278zx_status_t IntelHDACodec::DeviceIoctl(uint32_t op, 279 void* out_buf, 280 size_t out_len, 281 size_t* out_actual) { 282 dispatcher::Channel::ProcessHandler phandler( 283 [codec = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t { 284 OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_); 285 return codec->ProcessClientRequest(channel, false); 286 }); 287 288 return HandleDeviceIoctl(op, out_buf, out_len, out_actual, 289 default_domain_, 290 fbl::move(phandler), 291 nullptr); 292} 293 294#define PROCESS_CMD(_req_ack, _req_driver_chan, _ioctl, _payload, _handler) \ 295case _ioctl: \ 296 if (req_size != sizeof(req._payload)) { \ 297 LOG(TRACE, "Bad " #_payload \ 298 " request length (%u != %zu)\n", \ 299 req_size, sizeof(req._payload)); \ 300 return ZX_ERR_INVALID_ARGS; \ 301 } \ 302 if (_req_ack && (req.hdr.cmd & IHDA_NOACK_FLAG)) { \ 303 LOG(TRACE, "Cmd " #_payload \ 304 " requires acknowledgement, but the " \ 305 "NOACK flag was set!\n"); \ 306 return ZX_ERR_INVALID_ARGS; \ 307 } \ 308 if (_req_driver_chan && !is_driver_channel) { \ 309 LOG(TRACE, "Cmd " #_payload \ 310 " requires a privileged driver channel.\n"); \ 311 return ZX_ERR_ACCESS_DENIED; \ 312 } \ 313 return _handler(channel, req._payload) 314zx_status_t IntelHDACodec::ProcessClientRequest(dispatcher::Channel* channel, 315 bool is_driver_channel) { 316 zx_status_t res; 317 uint32_t req_size; 318 union { 319 ihda_proto::CmdHdr hdr; 320 ihda_proto::GetIDsReq get_ids; 321 ihda_proto::SendCORBCmdReq corb_cmd; 322 ihda_proto::RequestStreamReq request_stream; 323 ihda_proto::ReleaseStreamReq release_stream; 324 ihda_proto::SetStreamFmtReq set_stream_fmt; 325 } req; 326 // TODO(johngro) : How large is too large? 327 static_assert(sizeof(req) <= 256, "Request buffer is too large to hold on the stack!"); 328 329 // Read the client request. 330 ZX_DEBUG_ASSERT(channel != nullptr); 331 res = channel->Read(&req, sizeof(req), &req_size); 332 if (res != ZX_OK) { 333 LOG(TRACE, "Failed to read client request (res %d)\n", res); 334 return res; 335 } 336 337 // Sanity checks. 338 if (req_size < sizeof(req.hdr)) { 339 LOG(TRACE,"Client request too small to contain header (%u < %zu)\n", 340 req_size, sizeof(req.hdr)); 341 return ZX_ERR_INVALID_ARGS; 342 } 343 344 auto cmd_id = static_cast<ihda_cmd_t>(req.hdr.cmd & ~IHDA_NOACK_FLAG); 345 if (req.hdr.transaction_id == IHDA_INVALID_TRANSACTION_ID) { 346 LOG(TRACE, "Invalid transaction ID in client request 0x%04x\n", cmd_id); 347 return ZX_ERR_INVALID_ARGS; 348 } 349 350 // Dispatch 351 LOG(SPEW, "Client Request (cmd 0x%04x tid %u) len %u\n", 352 req.hdr.cmd, 353 req.hdr.transaction_id, 354 req_size); 355 356 switch (cmd_id) { 357 PROCESS_CMD(true, false, IHDA_CMD_GET_IDS, get_ids, ProcessGetIDs); 358 PROCESS_CMD(true, true, IHDA_CODEC_REQUEST_STREAM, request_stream, ProcessRequestStream); 359 PROCESS_CMD(false, true, IHDA_CODEC_RELEASE_STREAM, release_stream, ProcessReleaseStream); 360 PROCESS_CMD(false, true, IHDA_CODEC_SET_STREAM_FORMAT, set_stream_fmt, ProcessSetStreamFmt); 361 PROCESS_CMD(false, CodecVerb(req.corb_cmd.verb).is_set(), 362 IHDA_CODEC_SEND_CORB_CMD, corb_cmd, ProcessSendCORBCmd); 363 default: 364 LOG(TRACE, "Unrecognized command ID 0x%04x\n", req.hdr.cmd); 365 return ZX_ERR_INVALID_ARGS; 366 } 367} 368 369#undef PROCESS_CMD 370 371void IntelHDACodec::ProcessClientDeactivate(const dispatcher::Channel* channel) { 372 ZX_DEBUG_ASSERT(channel != nullptr); 373 374 // This should be the driver channel (client channels created with IOCTL do 375 // not register a deactivate handler). Start by releasing the internal 376 // channel reference from within the codec_driver_channel_lock. 377 { 378 fbl::AutoLock lock(&codec_driver_channel_lock_); 379 ZX_DEBUG_ASSERT(channel == codec_driver_channel_.get()); 380 codec_driver_channel_.reset(); 381 } 382 383 // Return any DMA streams the codec driver had owned back to the controller. 384 IntelHDAStream::Tree tmp; 385 { 386 fbl::AutoLock lock(&active_streams_lock_); 387 tmp = fbl::move(active_streams_); 388 } 389 390 while (!tmp.is_empty()) { 391 auto stream = tmp.pop_front(); 392 stream->Deactivate(); 393 controller_.ReturnStream(fbl::move(stream)); 394 } 395} 396 397zx_status_t IntelHDACodec::ProcessGetIDs(dispatcher::Channel* channel, 398 const ihda_proto::GetIDsReq& req) { 399 ZX_DEBUG_ASSERT(channel != nullptr); 400 401 ihda_proto::GetIDsResp resp; 402 resp.hdr = req.hdr; 403 resp.vid = props_.vid; 404 resp.did = props_.did; 405 resp.ihda_vmaj = props_.ihda_vmaj; 406 resp.ihda_vmin = props_.ihda_vmin; 407 resp.rev_id = props_.rev_id; 408 resp.step_id = props_.step_id; 409 410 return channel->Write(&resp, sizeof(resp)); 411} 412 413zx_status_t IntelHDACodec::ProcessSendCORBCmd(dispatcher::Channel* channel, 414 const ihda_proto::SendCORBCmdReq& req) { 415 ZX_DEBUG_ASSERT(channel != nullptr); 416 417 CodecVerb verb(req.verb); 418 419 // Make sure that the command is well formed. 420 if (!CodecCommand::SanityCheck(id(), req.nid, verb)) { 421 LOG(TRACE, "Bad SEND_CORB_CMD request values [%u, %hu, 0x%05x]\n", 422 id(), req.nid, verb.val); 423 return ZX_ERR_INVALID_ARGS; 424 } 425 426 fbl::RefPtr<dispatcher::Channel> chan_ref = (req.hdr.cmd & IHDA_NOACK_FLAG) 427 ? nullptr 428 : fbl::WrapRefPtr(channel); 429 430 auto job = CodecCmdJobAllocator::New(fbl::move(chan_ref), 431 req.hdr.transaction_id, 432 CodecCommand(id(), req.nid, verb)); 433 434 if (job == nullptr) 435 return ZX_ERR_NO_MEMORY; 436 437 zx_status_t res = controller_.QueueCodecCmd(fbl::move(job)); 438 if (res != ZX_OK) { 439 LOG(TRACE, "Failed to queue CORB command [%u, %hu, 0x%05x] (res %d)\n", 440 id(), req.nid, verb.val, res); 441 } 442 443 return res; 444} 445 446zx_status_t IntelHDACodec::ProcessRequestStream(dispatcher::Channel* channel, 447 const ihda_proto::RequestStreamReq& req) { 448 ZX_DEBUG_ASSERT(channel != nullptr); 449 450 ihda_proto::RequestStreamResp resp; 451 resp.hdr = req.hdr; 452 453 // Attempt to get a stream of the proper type. 454 auto type = req.input 455 ? IntelHDAStream::Type::INPUT 456 : IntelHDAStream::Type::OUTPUT; 457 auto stream = controller_.AllocateStream(type); 458 459 if (stream != nullptr) { 460 // Success, send its ID and its tag back to the codec and add it to the 461 // set of active streams owned by this codec. 462 resp.result = ZX_OK; 463 resp.stream_id = stream->id(); 464 resp.stream_tag = stream->tag(); 465 466 fbl::AutoLock lock(&active_streams_lock_); 467 active_streams_.insert(fbl::move(stream)); 468 } else { 469 // Failure; tell the codec that we are out of streams. 470 resp.result = ZX_ERR_NO_MEMORY; 471 resp.stream_id = 0; 472 resp.stream_tag = 0; 473 } 474 475 return channel->Write(&resp, sizeof(resp)); 476} 477 478zx_status_t IntelHDACodec::ProcessReleaseStream(dispatcher::Channel* channel, 479 const ihda_proto::ReleaseStreamReq& req) { 480 ZX_DEBUG_ASSERT(channel != nullptr); 481 482 // Remove the stream from the active set. 483 fbl::RefPtr<IntelHDAStream> stream; 484 { 485 fbl::AutoLock lock(&active_streams_lock_); 486 stream = active_streams_.erase(req.stream_id); 487 } 488 489 // If the stream was not active, our codec driver is crazy. Hang up the 490 // phone on it. 491 if (stream == nullptr) 492 return ZX_ERR_BAD_STATE; 493 494 // Give the stream back to the controller and (if an ack was requested) tell 495 // our codec driver that things went well. 496 stream->Deactivate(); 497 controller_.ReturnStream(fbl::move(stream)); 498 499 if (req.hdr.cmd & IHDA_NOACK_FLAG) 500 return ZX_OK; 501 502 ihda_proto::RequestStreamResp resp; 503 resp.hdr = req.hdr; 504 return channel->Write(&resp, sizeof(resp)); 505} 506 507zx_status_t IntelHDACodec::ProcessSetStreamFmt(dispatcher::Channel* channel, 508 const ihda_proto::SetStreamFmtReq& req) { 509 ZX_DEBUG_ASSERT(channel != nullptr); 510 511 // Sanity check the requested format. 512 if (!StreamFormat(req.format).SanityCheck()) { 513 LOG(TRACE, "Invalid encoded stream format 0x%04hx!\n", req.format); 514 return ZX_ERR_INVALID_ARGS; 515 } 516 517 // Grab a reference to the stream from the active set. 518 fbl::RefPtr<IntelHDAStream> stream; 519 { 520 fbl::AutoLock lock(&active_streams_lock_); 521 auto iter = active_streams_.find(req.stream_id); 522 if (iter.IsValid()) 523 stream = iter.CopyPointer(); 524 } 525 526 // If the stream was not active, our codec driver is crazy. Hang up the 527 // phone on it. 528 if (stream == nullptr) 529 return ZX_ERR_BAD_STATE; 530 531 // Set the stream format and assign the client channel to the stream. If 532 // this stream is already bound to a client, this will cause that connection 533 // to be closed. 534 zx::channel client_channel; 535 zx_status_t res = stream->SetStreamFormat(default_domain_, 536 req.format, 537 &client_channel); 538 if (res != ZX_OK) { 539 LOG(TRACE, "Failed to set stream format 0x%04hx for stream %hu (res %d)\n", 540 req.format, req.stream_id, res); 541 return res; 542 } 543 544 // Send the channel back to the codec driver. 545 ZX_DEBUG_ASSERT(client_channel.is_valid()); 546 ihda_proto::SetStreamFmtResp resp; 547 resp.hdr = req.hdr; 548 res = channel->Write(&resp, sizeof(resp), fbl::move(client_channel)); 549 550 if (res != ZX_OK) 551 LOG(TRACE, "Failed to send stream channel back to codec driver (res %d)\n", res); 552 553 return res; 554} 555 556zx_status_t IntelHDACodec::CodecGetDispatcherChannel(zx_handle_t* remote_endpoint_out) { 557 if (!remote_endpoint_out) 558 return ZX_ERR_INVALID_ARGS; 559 560 dispatcher::Channel::ProcessHandler phandler( 561 [codec = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t { 562 OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_); 563 return codec->ProcessClientRequest(channel, true); 564 }); 565 566 dispatcher::Channel::ChannelClosedHandler chandler( 567 [codec = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { 568 OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_); 569 codec->ProcessClientDeactivate(channel); 570 }); 571 572 // Enter the driver channel lock. If we have already connected to a codec 573 // driver, simply fail the request. Otherwise, attempt to build a driver channel 574 // and activate it. 575 fbl::AutoLock lock(&codec_driver_channel_lock_); 576 577 if (codec_driver_channel_ != nullptr) 578 return ZX_ERR_BAD_STATE; 579 580 zx::channel client_channel; 581 zx_status_t res; 582 res = CreateAndActivateChannel(default_domain_, 583 fbl::move(phandler), 584 fbl::move(chandler), 585 &codec_driver_channel_, 586 &client_channel); 587 if (res == ZX_OK) { 588 // If things went well, release the reference to the remote endpoint 589 // from the zx::channel instance into the unmanaged world of DDK 590 // protocols. 591 *remote_endpoint_out = client_channel.release(); 592 } 593 594 return res; 595} 596 597} // namespace intel_hda 598} // namespace audio 599