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