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 <ddk/device.h>
6#include <ddk/driver.h>
7#include <ddk/binding.h>
8#include <ddk/protocol/pci.h>
9#include <zircon/assert.h>
10#include <fbl/auto_call.h>
11#include <fbl/auto_lock.h>
12#include <stdio.h>
13#include <string.h>
14#include <threads.h>
15
16#include <dispatcher-pool/dispatcher-thread-pool.h>
17#include <intel-hda/utils/intel-hda-registers.h>
18#include <intel-hda/utils/intel-hda-proto.h>
19
20#include "debug-logging.h"
21#include "intel-hda-codec.h"
22#include "intel-hda-controller.h"
23#include "utils.h"
24
25namespace audio {
26namespace intel_hda {
27
28// static member variable declaration
29constexpr uint        IntelHDAController::RIRB_RESERVED_RESPONSE_SLOTS;
30fbl::atomic_uint32_t IntelHDAController::device_id_gen_(0u);
31
32// Device interface thunks
33#define DEV(_ctx)  static_cast<IntelHDAController*>(_ctx)
34zx_protocol_device_t IntelHDAController::CONTROLLER_DEVICE_THUNKS = {
35    .version      = DEVICE_OPS_VERSION,
36    .get_protocol = nullptr,
37    .open         = nullptr,
38    .open_at      = nullptr,
39    .close        = nullptr,
40    .unbind       = [](void* ctx) { DEV(ctx)->DeviceShutdown(); },
41    .release      = [](void* ctx) { DEV(ctx)->DeviceRelease(); },
42    .read         = nullptr,
43    .write        = nullptr,
44    .get_size     = nullptr,
45    .ioctl        = [](void*        ctx,
46                       uint32_t     op,
47                       const void*  in_buf,
48                       size_t       in_len,
49                       void*        out_buf,
50                       size_t       out_len,
51                       size_t*      out_actual) -> zx_status_t {
52                        return DEV(ctx)->DeviceIoctl(op, out_buf, out_len, out_actual);
53                   },
54    .suspend      = nullptr,
55    .resume       = nullptr,
56    .rxrpc        = nullptr,
57    .message      = nullptr,
58};
59#undef DEV
60
61IntelHDAController::IntelHDAController()
62    : state_(static_cast<StateStorage>(State::STARTING)),
63      id_(device_id_gen_.fetch_add(1u)) {
64    snprintf(log_prefix_, sizeof(log_prefix_), "IHDA Controller (unknown BDF)");
65}
66
67IntelHDAController::~IntelHDAController() {
68    ZX_DEBUG_ASSERT((GetState() == State::STARTING) || (GetState() == State::SHUT_DOWN));
69    // TODO(johngro) : place the device into reset.
70
71    // Release our register window.
72    mapped_regs_.Unmap();
73
74    // Release our IRQ.
75    irq_.reset();
76
77    // Disable IRQs at the PCI level.
78    if (pci_.ops != nullptr) {
79        ZX_DEBUG_ASSERT(pci_.ctx != nullptr);
80        pci_.ops->set_irq_mode(pci_.ctx, ZX_PCIE_IRQ_MODE_DISABLED, 0);
81    }
82
83    // Let go of our stream state.
84    free_input_streams_.clear();
85    free_output_streams_.clear();
86    free_bidir_streams_.clear();
87
88    // Unmap, unpin and release the memory we use for the command/response ring buffers.
89    cmd_buf_cpu_mem_.Unmap();
90    cmd_buf_hda_mem_.Unpin();
91
92    if (pci_.ops != nullptr) {
93        // TODO(johngro) : unclaim the PCI device.  Right now, there is no way
94        // to do this aside from closing the device handle (which would
95        // seriously mess up the DevMgr's brain)
96        pci_.ops = nullptr;
97        pci_.ctx = nullptr;
98    }
99}
100
101fbl::RefPtr<IntelHDAStream> IntelHDAController::AllocateStream(IntelHDAStream::Type type) {
102    fbl::AutoLock lock(&stream_pool_lock_);
103    IntelHDAStream::Tree* src;
104
105    switch (type) {
106    case IntelHDAStream::Type::INPUT:  src = &free_input_streams_;  break;
107    case IntelHDAStream::Type::OUTPUT: src = &free_output_streams_; break;
108
109    // Users are not allowed to directly request bidirectional stream contexts.
110    // It's just what they end up with if there are no other choices.
111    default:
112        ZX_DEBUG_ASSERT(false);
113        return nullptr;
114    }
115
116    if (src->is_empty()) {
117        src = &free_bidir_streams_;
118        if (src->is_empty())
119            return nullptr;
120    }
121
122    // Allocation fails if we cannot assign a unique tag to this stream.
123    uint8_t stream_tag = AllocateStreamTagLocked(type == IntelHDAStream::Type::INPUT);
124    if (!stream_tag)
125        return nullptr;
126
127    auto ret = src->pop_front();
128    ret->Configure(type, stream_tag);
129
130    return ret;
131}
132
133void IntelHDAController::ReturnStream(fbl::RefPtr<IntelHDAStream>&& ptr) {
134    fbl::AutoLock lock(&stream_pool_lock_);
135    ReturnStreamLocked(fbl::move(ptr));
136}
137
138void IntelHDAController::ReturnStreamLocked(fbl::RefPtr<IntelHDAStream>&& ptr) {
139    IntelHDAStream::Tree* dst;
140
141    ZX_DEBUG_ASSERT(ptr);
142
143    switch (ptr->type()) {
144    case IntelHDAStream::Type::INPUT:  dst = &free_input_streams_;  break;
145    case IntelHDAStream::Type::OUTPUT: dst = &free_output_streams_; break;
146    case IntelHDAStream::Type::BIDIR:  dst = &free_bidir_streams_;  break;
147    default: ZX_DEBUG_ASSERT(false); return;
148    }
149
150    ptr->Configure(IntelHDAStream::Type::INVALID, 0);
151    dst->insert(fbl::move(ptr));
152}
153
154uint8_t IntelHDAController::AllocateStreamTagLocked(bool input) {
155    uint16_t& tag_pool = input ? free_input_tags_ : free_output_tags_;
156
157    for (uint8_t ret = 1; ret < (sizeof(tag_pool) << 3); ++ret) {
158        if (tag_pool & (1u << ret)) {
159            tag_pool = static_cast<uint16_t>(tag_pool & ~(1u << ret));
160            return ret;
161        }
162    }
163
164    return 0;
165}
166
167void IntelHDAController::ReleaseStreamTagLocked(bool input, uint8_t tag) {
168    uint16_t& tag_pool = input ? free_input_tags_ : free_output_tags_;
169
170    ZX_DEBUG_ASSERT((tag > 0) && (tag <= 15));
171    ZX_DEBUG_ASSERT((tag_pool & (1u << tag)) == 0);
172
173    tag_pool = static_cast<uint16_t>((tag_pool | (1u << tag)));
174}
175
176void IntelHDAController::DeviceShutdown() {
177    // Make sure we have closed all of the event sources (eg. IRQs, wakeup
178    // events, channels clients are using to talk to us, etc..) and that we have
179    // synchronized with any dispatch callbacks in flight.
180    default_domain_->Deactivate();
181
182    // Disable all interrupts and place the device into reset on our way out.
183    if (regs() != nullptr) {
184        REG_WR(&regs()->intctl, 0u);
185        REG_CLR_BITS(&regs()->gctl, HDA_REG_GCTL_HWINIT);
186    }
187
188    // Shutdown and clean up all of our codecs.
189    for (auto& codec_ptr : codecs_) {
190        if (codec_ptr != nullptr) {
191            codec_ptr->Shutdown();
192            codec_ptr.reset();
193        }
194    }
195
196    // Any CORB jobs we may have had in progress may be discarded.
197    {
198        fbl::AutoLock corb_lock(&corb_lock_);
199        in_flight_corb_jobs_.clear();
200        pending_corb_jobs_.clear();
201    }
202
203    // Done.  Clearly mark that we are now shut down.
204    SetState(State::SHUT_DOWN);
205}
206
207void IntelHDAController::DeviceRelease() {
208    // Take our unmanaged reference back from our published device node.
209    auto thiz = fbl::internal::MakeRefPtrNoAdopt(this);
210
211    // ASSERT that we have been properly shut down, then release the DDK's
212    // reference to our state as we allow thiz to go out of scope.
213    ZX_DEBUG_ASSERT(GetState() == State::SHUT_DOWN);
214    thiz.reset();
215}
216
217zx_status_t IntelHDAController::DeviceIoctl(uint32_t op,
218                                            void*    out_buf,
219                                            size_t   out_len,
220                                            size_t*  out_actual) {
221    dispatcher::Channel::ProcessHandler phandler(
222    [controller = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t {
223        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, controller->default_domain_);
224        return controller->ProcessClientRequest(channel);
225    });
226
227    return HandleDeviceIoctl(op, out_buf, out_len, out_actual,
228                             default_domain_,
229                             fbl::move(phandler),
230                             nullptr);
231}
232
233void IntelHDAController::RootDeviceRelease() {
234    // Take our unmanaged reference back from our published device node.
235    auto thiz = fbl::internal::MakeRefPtrNoAdopt(this);
236    // Now let go of it.
237    thiz.reset();
238}
239
240zx_status_t IntelHDAController::ProcessClientRequest(dispatcher::Channel* channel) {
241    zx_status_t res;
242    uint32_t req_size;
243    union RequestBuffer {
244        ihda_cmd_hdr_t                      hdr;
245        ihda_get_ids_req_t                  get_ids;
246        ihda_controller_snapshot_regs_req_t snapshot_regs;
247    } req;
248
249    // TODO(johngro) : How large is too large?
250    static_assert(sizeof(req) <= 256, "Request buffer is too large to hold on the stack!");
251
252    // Read the client request.
253    ZX_DEBUG_ASSERT(channel != nullptr);
254    res = channel->Read(&req, sizeof(req), &req_size);
255    if (res != ZX_OK) {
256        LOG(TRACE, "Failed to read client request (res %d)\n", res);
257        return res;
258    }
259
260    // Sanity checks
261    if (req_size < sizeof(req.hdr)) {
262        LOG(TRACE, "Client request too small to contain header (%u < %zu)\n",
263                req_size, sizeof(req.hdr));
264        return ZX_ERR_INVALID_ARGS;
265    }
266
267    // Dispatch
268    LOG(SPEW, "Client Request 0x%04x len %u\n", req.hdr.cmd, req_size);
269    switch (req.hdr.cmd) {
270    case IHDA_CMD_GET_IDS: {
271        if (req_size != sizeof(req.get_ids)) {
272            LOG(TRACE, "Bad GET_IDS request length (%u != %zu)\n",
273                    req_size, sizeof(req.get_ids));
274            return ZX_ERR_INVALID_ARGS;
275        }
276
277        ZX_DEBUG_ASSERT(pci_dev_ != nullptr);
278        ZX_DEBUG_ASSERT(regs() != nullptr);
279
280        ihda_get_ids_resp_t resp;
281        resp.hdr       = req.hdr;
282        resp.vid       = pci_dev_info_.vendor_id;
283        resp.did       = pci_dev_info_.device_id;
284        resp.ihda_vmaj = REG_RD(&regs()->vmaj);
285        resp.ihda_vmin = REG_RD(&regs()->vmin);
286        resp.rev_id    = 0;
287        resp.step_id   = 0;
288
289        return channel->Write(&resp, sizeof(resp));
290    }
291
292    case IHDA_CONTROLLER_CMD_SNAPSHOT_REGS:
293        if (req_size != sizeof(req.snapshot_regs)) {
294            LOG(TRACE, "Bad SNAPSHOT_REGS request length (%u != %zu)\n",
295                    req_size, sizeof(req.snapshot_regs));
296            return ZX_ERR_INVALID_ARGS;
297        }
298
299        return SnapshotRegs(channel, req.snapshot_regs);
300
301    default:
302        return ZX_ERR_INVALID_ARGS;
303    }
304}
305
306#define DEV(_ctx)  static_cast<IntelHDAController*>(_ctx)
307zx_protocol_device_t IntelHDAController::ROOT_DEVICE_THUNKS = {
308    .version      = DEVICE_OPS_VERSION,
309    .get_protocol = nullptr,
310    .open         = nullptr,
311    .open_at      = nullptr,
312    .close        = nullptr,
313    .unbind       = nullptr,
314    .release      = [](void* ctx) { DEV(ctx)->RootDeviceRelease(); },
315    .read         = nullptr,
316    .write        = nullptr,
317    .get_size     = nullptr,
318    .ioctl        = nullptr,
319    .suspend      = nullptr,
320    .resume       = nullptr,
321    .rxrpc        = nullptr,
322    .message      = nullptr,
323};
324#undef DEV
325
326zx_status_t IntelHDAController::DriverInit(void** out_ctx) {
327    // Note: It is assumed that calls to Init/Release are serialized by the
328    // pci_dev manager.  If this assumption ever needs to be relaxed, explicit
329    // serialization will need to be added here.
330
331    return ZX_OK;
332}
333
334zx_status_t IntelHDAController::DriverBind(void* ctx,
335                                           zx_device_t* device) {
336    fbl::AllocChecker ac;
337    fbl::RefPtr<IntelHDAController> controller(fbl::AdoptRef(new (&ac) IntelHDAController()));
338
339    if (!ac.check()) {
340        return ZX_ERR_NO_MEMORY;
341    }
342
343    zx_status_t ret = controller->Init(device);
344    if (ret != ZX_OK) {
345        return ret;
346    }
347
348    // Initialize our device and fill out the protocol hooks
349    device_add_args_t args = { };
350    args.version = DEVICE_ADD_ARGS_VERSION;
351    args.name = "intel-hda-controller";
352    {
353        // use a different refptr to avoid problems in error path
354        auto ddk_ref = controller;
355        args.ctx = ddk_ref.leak_ref();
356    }
357    args.ops =  &ROOT_DEVICE_THUNKS;
358    args.flags = DEVICE_ADD_NON_BINDABLE;
359
360    // Publish the device.
361    ret = device_add(device, &args, nullptr);
362    if (ret != ZX_OK) {
363        controller.reset();
364    }
365    return ret;
366}
367
368void IntelHDAController::DriverRelease(void* ctx) {
369    // If we are the last one out the door, turn off the lights in the thread pool.
370    dispatcher::ThreadPool::ShutdownAll();
371}
372
373}  // namespace intel_hda
374}  // namespace audio
375
376extern "C" {
377zx_status_t ihda_init_hook(void** out_ctx) {
378    zx_status_t res = ::audio::intel_hda::DriverVmars::Initialize();
379
380    if (res == ZX_OK) {
381        res = ::audio::intel_hda::IntelHDAController::DriverInit(out_ctx);
382    }
383
384    if (res != ZX_OK) {
385        ::audio::intel_hda::DriverVmars::Shutdown();
386    }
387
388    return res;
389}
390
391zx_status_t ihda_bind_hook(void* ctx, zx_device_t* pci_dev) {
392    return ::audio::intel_hda::IntelHDAController::DriverBind(ctx, pci_dev);
393}
394
395void ihda_release_hook(void* ctx) {
396    ::audio::intel_hda::IntelHDAController::DriverRelease(ctx);
397    ::audio::intel_hda::DriverVmars::Shutdown();
398}
399}  // extern "C"
400
401