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