1// Copyright 2018 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 <fbl/alloc_checker.h>
6#include <fbl/auto_lock.h>
7#include <zircon/assert.h>
8
9#include "intel-hda-dsp.h"
10#include "intel-hda-controller.h"
11#include "utils.h"
12
13namespace audio {
14namespace intel_hda {
15
16#define DEV  (static_cast<IntelHDADSP*>(ctx))
17zx_protocol_device_t IntelHDADSP::DSP_DEVICE_THUNKS = {
18    .version      = DEVICE_OPS_VERSION,
19    .get_protocol = [](void* ctx,
20                       uint32_t proto_id,
21                       void* protocol) -> zx_status_t
22                    {
23                        return DEV->DeviceGetProtocol(proto_id, protocol);
24                    },
25    .open         = nullptr,
26    .open_at      = nullptr,
27    .close        = nullptr,
28    .unbind       = [](void* ctx)
29                    {
30                        DEV->DeviceUnbind();
31                    },
32    .release      = nullptr,
33    .read         = nullptr,
34    .write        = nullptr,
35    .get_size     = nullptr,
36    .ioctl        = [](void* ctx,
37                       uint32_t op,
38                       const void* in_buf,
39                       size_t in_len,
40                       void* out_buf,
41                       size_t out_len,
42                       size_t* out_actual) -> zx_status_t
43                    {
44                        return DEV->DeviceIoctl(op, out_buf, out_len, out_actual);
45                    },
46    .suspend      = nullptr,
47    .resume       = nullptr,
48    .rxrpc        = nullptr,
49    .message      = nullptr,
50};
51
52ihda_dsp_protocol_ops_t IntelHDADSP::DSP_PROTO_THUNKS = {
53    .get_dev_info = [](void* ctx, zx_pcie_device_info_t* out_info)
54    {
55        ZX_DEBUG_ASSERT(ctx);
56        return DEV->GetDevInfo(out_info);
57    },
58    .get_mmio = [](void* ctx, zx_handle_t* out_vmo, size_t* out_size) -> zx_status_t
59    {
60        ZX_DEBUG_ASSERT(ctx);
61        return DEV->GetMmio(out_vmo, out_size);
62    },
63    .get_bti = [](void* ctx, zx_handle_t* out_handle) -> zx_status_t
64    {
65        ZX_DEBUG_ASSERT(ctx);
66        return DEV->GetBti(out_handle);
67    },
68    .enable = [](void* ctx)
69    {
70        ZX_DEBUG_ASSERT(ctx);
71        DEV->Enable();
72    },
73    .disable = [](void* ctx)
74    {
75        ZX_DEBUG_ASSERT(ctx);
76        DEV->Disable();
77    },
78    .irq_enable = [](void* ctx, ihda_dsp_irq_callback_t* callback, void* cookie) -> zx_status_t
79    {
80        ZX_DEBUG_ASSERT(ctx);
81        return DEV->IrqEnable(callback, cookie);
82    },
83    .irq_disable = [](void* ctx)
84    {
85        ZX_DEBUG_ASSERT(ctx);
86        return DEV->IrqDisable();
87    }
88};
89
90ihda_codec_protocol_ops_t IntelHDADSP::CODEC_PROTO_THUNKS = {
91    .get_driver_channel = [](void* ctx, zx_handle_t* channel_out) -> zx_status_t
92    {
93        ZX_DEBUG_ASSERT(ctx);
94        return DEV->CodecGetDispatcherChannel(channel_out);
95    },
96};
97#undef DEV
98
99IntelHDADSP::IntelHDADSP(IntelHDAController& controller,
100                         hda_pp_registers_t* pp_regs,
101                         const fbl::RefPtr<RefCountedBti>& pci_bti)
102    : controller_(controller),
103      pp_regs_(pp_regs),
104      pci_bti_(pci_bti) {
105    default_domain_ = dispatcher::ExecutionDomain::Create();
106
107    const auto& info = controller_.dev_info();
108    snprintf(log_prefix_, sizeof(log_prefix_),
109             "IHDA DSP %02x:%02x.%01x",
110             info.bus_id, info.dev_id, info.func_id);
111}
112
113fbl::RefPtr<IntelHDADSP> IntelHDADSP::Create(IntelHDAController& controller,
114                                             hda_pp_registers_t* pp_regs,
115                                             const fbl::RefPtr<RefCountedBti>& pci_bti) {
116    fbl::AllocChecker ac;
117    auto ret = fbl::AdoptRef(new (&ac) IntelHDADSP(controller, pp_regs, pci_bti));
118    if (!ac.check()) {
119        GLOBAL_LOG(ERROR, "Out of memory attempting to allocate DSP\n");
120        return nullptr;
121    }
122
123    if (ret->default_domain_ == nullptr) {
124        GLOBAL_LOG(ERROR, "Out of memory attempting to allocate execution domain\n");
125        return nullptr;
126    }
127
128    zx_status_t res = ret->PublishDevice();
129    if (res != ZX_OK) {
130        GLOBAL_LOG(ERROR, "Failed to publish DSP device (res %d)\n", res);
131        return nullptr;
132    }
133
134    return ret;
135}
136
137zx_status_t IntelHDADSP::PublishDevice() {
138    char dev_name[ZX_DEVICE_NAME_MAX] = { 0 };
139    snprintf(dev_name, sizeof(dev_name), "intel-sst-dsp-%03u", controller_.id());
140
141    zx_device_prop_t props[3];
142    props[0].id = BIND_PROTOCOL;
143    props[0].value = ZX_PROTOCOL_IHDA_DSP;
144    props[1].id = BIND_PCI_VID;
145    props[1].value = controller_.dev_info().vendor_id;
146    props[2].id = BIND_PCI_DID;
147    props[2].value = controller_.dev_info().device_id;
148
149    device_add_args_t args = {};
150    args.version = DEVICE_ADD_ARGS_VERSION;
151    args.name = dev_name;
152    args.ctx = this;
153    args.ops = &DSP_DEVICE_THUNKS;
154    args.proto_id = ZX_PROTOCOL_IHDA_DSP;
155    args.props = props;
156    args.prop_count = countof(props);
157
158    return device_add(controller_.dev_node(), &args, &dev_node_);
159}
160
161void IntelHDADSP::ProcessIRQ() {
162    fbl::AutoLock dsp_lock(&dsp_lock_);
163    if (pp_regs() == nullptr) {
164        return;
165    }
166    if (irq_callback_ == nullptr) {
167        return;
168    }
169    ZX_DEBUG_ASSERT(irq_cookie_ != nullptr);
170    ZX_DEBUG_ASSERT(pp_regs() != nullptr);
171    uint32_t ppsts = REG_RD(&pp_regs()->ppsts);
172    if (!(ppsts & HDA_PPSTS_PIS)) {
173        return;
174    }
175    irq_callback_(irq_cookie_);
176}
177
178zx_status_t IntelHDADSP::DeviceGetProtocol(uint32_t proto_id, void* protocol) {
179    switch (proto_id) {
180    case ZX_PROTOCOL_IHDA_CODEC: {
181        auto proto = static_cast<ihda_codec_protocol_t*>(protocol);
182        proto->ops = &CODEC_PROTO_THUNKS;
183        proto->ctx = this;
184        return ZX_OK;
185    }
186    case ZX_PROTOCOL_IHDA_DSP: {
187        auto proto = static_cast<ihda_dsp_protocol_t*>(protocol);
188        proto->ops = &DSP_PROTO_THUNKS;
189        proto->ctx = this;
190        return ZX_OK;
191    }
192    default:
193        LOG(ERROR, "Unsupported protocol 0x%08x\n", proto_id);
194        return ZX_ERR_NOT_SUPPORTED;
195    }
196}
197
198zx_status_t IntelHDADSP::DeviceIoctl(uint32_t op,
199                                     void*    out_buf,
200                                     size_t   out_len,
201                                     size_t*  out_actual) {
202    dispatcher::Channel::ProcessHandler phandler(
203    [dsp = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t {
204        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, dsp->default_domain_);
205        return dsp->ProcessClientRequest(channel, false);
206    });
207
208    return HandleDeviceIoctl(op, out_buf, out_len, out_actual,
209                             default_domain_,
210                             fbl::move(phandler),
211                             nullptr);
212}
213
214void IntelHDADSP::DeviceUnbind() {
215    // Close all existing connections and synchronize with any client threads
216    // who are currently processing requests.
217    default_domain_->Deactivate();
218
219    // Give any active streams we had back to our controller.
220    IntelHDAStream::Tree streams;
221    {
222        fbl::AutoLock lock(&active_streams_lock_);
223        streams.swap(active_streams_);
224    }
225
226    while (!streams.is_empty()) {
227        controller_.ReturnStream(streams.pop_front());
228    }
229}
230
231void IntelHDADSP::GetDevInfo(zx_pcie_device_info_t* out_info) {
232    if (!out_info) {
233        return;
234    }
235    memcpy(out_info, &controller_.dev_info(), sizeof(*out_info));
236}
237
238zx_status_t IntelHDADSP::GetMmio(zx_handle_t* out_vmo, size_t* out_size) {
239    // Fetch the BAR which the Audio DSP registers (BAR 4), then sanity check the type
240    // and size.
241    zx_pci_bar_t bar_info;
242    zx_status_t res = pci_get_bar(controller_.pci(), 4u, &bar_info);
243    if (res != ZX_OK) {
244        LOG(ERROR, "Error attempting to fetch registers from PCI (res %d)\n", res);
245        return res;
246    }
247
248    if (bar_info.type != ZX_PCI_BAR_TYPE_MMIO) {
249        LOG(ERROR, "Bad register window type (expected %u got %u)\n",
250                ZX_PCI_BAR_TYPE_MMIO, bar_info.type);
251        return ZX_ERR_INTERNAL;
252    }
253
254    *out_vmo = bar_info.handle;
255    *out_size = bar_info.size;
256    return ZX_OK;
257}
258
259zx_status_t IntelHDADSP::GetBti(zx_handle_t* out_handle) {
260    ZX_DEBUG_ASSERT(pci_bti_ != nullptr);
261    zx::bti bti;
262    zx_status_t res = pci_bti_->initiator().duplicate(ZX_RIGHT_SAME_RIGHTS, &bti);
263    if (res != ZX_OK) {
264        LOG(ERROR, "Error duplicating BTI for DSP (res %d)\n", res);
265        return res;
266    }
267    *out_handle = bti.release();
268    return ZX_OK;
269}
270
271void IntelHDADSP::Enable() {
272    // Note: The GPROCEN bit does not really enable or disable the Audio DSP
273    // operation, but mainly to work around some legacy Intel HD Audio driver
274    // software such that if GPROCEN = 0, ADSPxBA (BAR2) is mapped to the Intel
275    // HD Audio memory mapped configuration registers, for compliancy with some
276    // legacy SW implementation. If GPROCEN = 1, only then ADSPxBA (BAR2) is
277    // mapped to the actual Audio DSP memory mapped configuration registers.
278    REG_SET_BITS<uint32_t>(&pp_regs()->ppctl, HDA_PPCTL_GPROCEN);
279}
280
281void IntelHDADSP::Disable() {
282    REG_WR(&pp_regs()->ppctl, 0u);
283}
284
285zx_status_t IntelHDADSP::IrqEnable(ihda_dsp_irq_callback_t* callback, void* cookie) {
286    fbl::AutoLock dsp_lock(&dsp_lock_);
287    if (irq_callback_ != nullptr) {
288        return ZX_ERR_ALREADY_EXISTS;
289    }
290    ZX_DEBUG_ASSERT(irq_cookie_ == nullptr);
291
292    irq_callback_ = callback;
293    irq_cookie_ = cookie;
294
295    REG_SET_BITS<uint32_t>(&pp_regs()->ppctl, HDA_PPCTL_PIE);
296
297    return ZX_OK;
298}
299
300void IntelHDADSP::IrqDisable() {
301    fbl::AutoLock dsp_lock(&dsp_lock_);
302    REG_CLR_BITS<uint32_t>(&pp_regs()->ppctl, HDA_PPCTL_PIE);
303    irq_callback_ = nullptr;
304    irq_cookie_ = nullptr;
305}
306
307zx_status_t IntelHDADSP::CodecGetDispatcherChannel(zx_handle_t* remote_endpoint_out) {
308    if (!remote_endpoint_out)
309        return ZX_ERR_INVALID_ARGS;
310
311    dispatcher::Channel::ProcessHandler phandler(
312    [codec = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t {
313        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_);
314        return codec->ProcessClientRequest(channel, true);
315    });
316
317    dispatcher::Channel::ChannelClosedHandler chandler(
318    [codec = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void {
319        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_);
320        codec->ProcessClientDeactivate(channel);
321    });
322
323    // Enter the driver channel lock.  If we have already connected to a codec
324    // driver, simply fail the request.  Otherwise, attempt to build a driver channel
325    // and activate it.
326    fbl::AutoLock lock(&codec_driver_channel_lock_);
327
328    if (codec_driver_channel_ != nullptr)
329        return ZX_ERR_BAD_STATE;
330
331    zx::channel client_channel;
332    zx_status_t res;
333    res = CreateAndActivateChannel(default_domain_,
334                                   fbl::move(phandler),
335                                   fbl::move(chandler),
336                                   &codec_driver_channel_,
337                                   &client_channel);
338    if (res == ZX_OK) {
339        // If things went well, release the reference to the remote endpoint
340        // from the zx::channel instance into the unmanaged world of DDK
341        // protocols.
342        *remote_endpoint_out = client_channel.release();
343    }
344
345    return res;
346}
347
348#define PROCESS_CMD(_req_ack, _req_driver_chan, _ioctl, _payload, _handler) \
349case _ioctl:                                                                \
350    if (req_size != sizeof(req._payload)) {                                 \
351        LOG(TRACE, "Bad " #_payload                                         \
352                   " request length (%u != %zu)\n",                         \
353                   req_size, sizeof(req._payload));                         \
354        return ZX_ERR_INVALID_ARGS;                                         \
355    }                                                                       \
356    if (_req_ack && (req.hdr.cmd & IHDA_NOACK_FLAG))  {                     \
357        LOG(TRACE, "Cmd " #_payload                                         \
358                   " requires acknowledgement, but the "                    \
359                   "NOACK flag was set!\n");                                \
360        return ZX_ERR_INVALID_ARGS;                                         \
361    }                                                                       \
362    if (_req_driver_chan  && !is_driver_channel) {                          \
363        LOG(TRACE, "Cmd " #_payload                                         \
364                   " requires a privileged driver channel.\n");             \
365        return ZX_ERR_ACCESS_DENIED;                                        \
366    }                                                                       \
367    return _handler(channel, req._payload)
368zx_status_t IntelHDADSP::ProcessClientRequest(dispatcher::Channel* channel,
369                                                bool is_driver_channel) {
370    zx_status_t res;
371    uint32_t req_size;
372    union {
373        ihda_proto::CmdHdr           hdr;
374        ihda_proto::RequestStreamReq request_stream;
375        ihda_proto::ReleaseStreamReq release_stream;
376        ihda_proto::SetStreamFmtReq  set_stream_fmt;
377    } req;
378    // TODO(johngro) : How large is too large?
379    static_assert(sizeof(req) <= 256, "Request buffer is too large to hold on the stack!");
380
381    // Read the client request.
382    ZX_DEBUG_ASSERT(channel != nullptr);
383    res = channel->Read(&req, sizeof(req), &req_size);
384    if (res != ZX_OK) {
385        LOG(TRACE, "Failed to read client request (res %d)\n", res);
386        return res;
387    }
388
389    // Sanity checks.
390    if (req_size < sizeof(req.hdr)) {
391        LOG(TRACE,"Client request too small to contain header (%u < %zu)\n",
392                req_size, sizeof(req.hdr));
393        return ZX_ERR_INVALID_ARGS;
394    }
395
396    auto cmd_id = static_cast<ihda_cmd_t>(req.hdr.cmd & ~IHDA_NOACK_FLAG);
397    if (req.hdr.transaction_id == IHDA_INVALID_TRANSACTION_ID) {
398        LOG(TRACE, "Invalid transaction ID in client request 0x%04x\n", cmd_id);
399        return ZX_ERR_INVALID_ARGS;
400    }
401
402    // Dispatch
403    LOG(SPEW, "Client Request (cmd 0x%04x tid %u) len %u\n",
404            req.hdr.cmd,
405            req.hdr.transaction_id,
406            req_size);
407
408    switch (cmd_id) {
409    PROCESS_CMD(true,  true,  IHDA_CODEC_REQUEST_STREAM,    request_stream, ProcessRequestStream);
410    PROCESS_CMD(false, true,  IHDA_CODEC_RELEASE_STREAM,    release_stream, ProcessReleaseStream);
411    PROCESS_CMD(false, true,  IHDA_CODEC_SET_STREAM_FORMAT, set_stream_fmt, ProcessSetStreamFmt);
412    default:
413        LOG(TRACE, "Unrecognized command ID 0x%04x\n", req.hdr.cmd);
414        return ZX_ERR_INVALID_ARGS;
415    }
416}
417
418#undef PROCESS_CMD
419
420void IntelHDADSP::ProcessClientDeactivate(const dispatcher::Channel* channel) {
421    ZX_DEBUG_ASSERT(channel != nullptr);
422
423    // This should be the driver channel (client channels created with IOCTL do
424    // not register a deactivate handler).  Start by releasing the internal
425    // channel reference from within the codec_driver_channel_lock.
426    {
427        fbl::AutoLock lock(&codec_driver_channel_lock_);
428        ZX_DEBUG_ASSERT(channel == codec_driver_channel_.get());
429        codec_driver_channel_.reset();
430    }
431
432    // Return any DMA streams the codec driver had owned back to the controller.
433    IntelHDAStream::Tree tmp;
434    {
435        fbl::AutoLock lock(&active_streams_lock_);
436        tmp = fbl::move(active_streams_);
437    }
438
439    while (!tmp.is_empty()) {
440        auto stream = tmp.pop_front();
441        stream->Deactivate();
442        controller_.ReturnStream(fbl::move(stream));
443    }
444}
445
446zx_status_t IntelHDADSP::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        LOG(TRACE, "Decouple stream #%u\n", stream->id());
461        // Decouple stream
462        REG_SET_BITS<uint32_t>(&pp_regs()->ppctl, (1 << stream->dma_id()));
463
464        // Success, send its ID and its tag back to the codec and add it to the
465        // set of active streams owned by this codec.
466        resp.result     = ZX_OK;
467        resp.stream_id  = stream->id();
468        resp.stream_tag = stream->tag();
469
470        fbl::AutoLock lock(&active_streams_lock_);
471        active_streams_.insert(fbl::move(stream));
472    } else {
473        // Failure; tell the codec that we are out of streams.
474        resp.result     = ZX_ERR_NO_MEMORY;
475        resp.stream_id  = 0;
476        resp.stream_tag = 0;
477    }
478
479    return channel->Write(&resp, sizeof(resp));
480}
481
482zx_status_t IntelHDADSP::ProcessReleaseStream(dispatcher::Channel* channel,
483                                                const ihda_proto::ReleaseStreamReq& req) {
484    ZX_DEBUG_ASSERT(channel != nullptr);
485
486    // Remove the stream from the active set.
487    fbl::RefPtr<IntelHDAStream> stream;
488    {
489        fbl::AutoLock lock(&active_streams_lock_);
490        stream = active_streams_.erase(req.stream_id);
491    }
492
493    // If the stream was not active, our codec driver is crazy.  Hang up the
494    // phone on it.
495    if (stream == nullptr)
496        return ZX_ERR_BAD_STATE;
497
498    LOG(TRACE, "Couple stream #%u\n", stream->id());
499
500    // Couple stream
501    REG_CLR_BITS<uint32_t>(&pp_regs()->ppctl, (1 << stream->dma_id()));
502
503    // Give the stream back to the controller and (if an ack was requested) tell
504    // our codec driver that things went well.
505    stream->Deactivate();
506    controller_.ReturnStream(fbl::move(stream));
507
508    if (req.hdr.cmd & IHDA_NOACK_FLAG)
509        return ZX_OK;
510
511    ihda_proto::RequestStreamResp resp;
512    resp.hdr = req.hdr;
513    return channel->Write(&resp, sizeof(resp));
514}
515
516zx_status_t IntelHDADSP::ProcessSetStreamFmt(dispatcher::Channel* channel,
517                                               const ihda_proto::SetStreamFmtReq& req) {
518    ZX_DEBUG_ASSERT(channel != nullptr);
519
520    // Sanity check the requested format.
521    if (!StreamFormat(req.format).SanityCheck()) {
522        LOG(TRACE, "Invalid encoded stream format 0x%04hx!\n", req.format);
523        return ZX_ERR_INVALID_ARGS;
524    }
525
526    // Grab a reference to the stream from the active set.
527    fbl::RefPtr<IntelHDAStream> stream;
528    {
529        fbl::AutoLock lock(&active_streams_lock_);
530        auto iter = active_streams_.find(req.stream_id);
531        if (iter.IsValid())
532            stream = iter.CopyPointer();
533    }
534
535    // If the stream was not active, our codec driver is crazy.  Hang up the
536    // phone on it.
537    if (stream == nullptr)
538        return ZX_ERR_BAD_STATE;
539
540    // Set the stream format and assign the client channel to the stream.  If
541    // this stream is already bound to a client, this will cause that connection
542    // to be closed.
543    zx::channel client_channel;
544    zx_status_t res = stream->SetStreamFormat(default_domain_,
545                                              req.format,
546                                              &client_channel);
547    if (res != ZX_OK) {
548        LOG(TRACE, "Failed to set stream format 0x%04hx for stream %hu (res %d)\n",
549                  req.format, req.stream_id, res);
550        return res;
551    }
552
553    // Send the channel back to the codec driver.
554    ZX_DEBUG_ASSERT(client_channel.is_valid());
555    ihda_proto::SetStreamFmtResp resp;
556    resp.hdr = req.hdr;
557    res = channel->Write(&resp, sizeof(resp), fbl::move(client_channel));
558
559    if (res != ZX_OK)
560        LOG(TRACE, "Failed to send stream channel back to codec driver (res %d)\n", res);
561
562    return res;
563}
564
565}  // namespace intel_hda
566}  // namespace audio
567