/* * Copyright (C) 2009-2010 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include #include #include "iop_audio_protocol.h" #include "AudioCodecA5.h" #include "clock_stepping.h" #ifdef DO_PROFILING // doing peak heap check requires heap.c to support it //#define DO_PEAKHEAPCHECK // doing peak heap check requires task.c to support it //#define DO_PEAKSTACKCHECK #include #include #define kPerformanceSize 16 struct PerformanceDetails { bool valid; utime_t time; uint32_t cycles; }; struct PerformanceDetails performanceDetails[kPerformanceSize]; void PerformanceSnapShotReset() { memset(performanceDetails, 0, sizeof(performanceDetails)); } void PerformanceSnapShotStart(uint32_t which) { if (which >= kPerformanceSize) return; performanceDetails[which].time = system_time(); performanceDetails[which].cycles = arm_read_pmreg(ARM_PMCCNTR); } void PerformanceSnapShotEnd(uint32_t which) { if (which >= kPerformanceSize) return; performanceDetails[which].valid = true; performanceDetails[which].time = system_time() - performanceDetails[which].time; performanceDetails[which].cycles = arm_read_pmreg(ARM_PMCCNTR) - performanceDetails[which].cycles; } void SetupProfiling(IOPAUDIO_METRICS* metrics) { // mapping from enum to event monitors from the A5 TRM static const uint32_t eventtable[ACLKCycles-ICacheAccesses] = { 0x14, 0x1, 0x4, 0x3, 0x6, 0x7, 0xF, 0x8, 0x12, 0x10 }; // reset our user defined performance counters. PerformanceSnapShotReset(); // select wich events we are going to monitor uint32_t evt0 = 0x03; // by default we measure dcache fills uint32_t evt1 = 0x04; // by default we measure dcache accesses uint32_t i = ICacheAccesses; // choose the event monitor to follow. We only choose the first two we find for (;i < ACLKCycles; ++i) { if (metrics->mMetricsItemsValid & (1 << i)) { evt0 = eventtable[(i - ICacheAccesses)]; break; } } ++i; for (;i < ACLKCycles; ++i) { if (metrics->mMetricsItemsValid & (1 << i)) { evt1 = eventtable[(i - ICacheAccesses)]; break; } } #ifdef DO_PEAKSTACKCHECK if (metrics->mMetricsItemsValid & (1<mMetricsItemsValid & (1<mMetricsFields[ProcessTimeUSec] = system_time(); metrics->mMetricsFields[ProcessCycles] = arm_read_pmreg(ARM_PMCCNTR); } void EndProfiling(IOPAUDIO_METRICS* metrics) { uint32_t evt0, evt1, cycles; arm_write_pmreg(ARM_PMCNTENCLR, ~0); /* disable all counters */ utime_t timeEnd = system_time(); arm_write_pmreg(ARM_PMSELR, 0); evt0 = arm_read_pmreg(ARM_PMXEVCNTR); /* read event 0 */ arm_write_pmreg(ARM_PMSELR, 1); evt1 = arm_read_pmreg(ARM_PMXEVCNTR); /* read event 1 */ cycles = arm_read_pmreg(ARM_PMCCNTR); /* read cycles */ if (arm_read_pmreg(ARM_PMOVSR)) dprintf(DEBUG_CRITICAL, "*** counter overflow ***\n"); uint32_t i = ICacheAccesses; // choose the event monitor to follow. We only choose the first two we find for (;i < ACLKCycles; ++i) { if (metrics->mMetricsItemsValid & (1 << i)) { metrics->mMetricsFields[i] = evt0; break; } } ++i; for (;i < ACLKCycles; ++i) { if (metrics->mMetricsItemsValid & (1 << i)) { metrics->mMetricsFields[i] = evt1; break; } } if (metrics->mMetricsItemsValid & (1<mMetricsFields[ProcessTimeUSec] = timeEnd - metrics->mMetricsFields[ProcessTimeUSec]; if (metrics->mMetricsItemsValid & (1<mMetricsFields[ProcessCycles] = cycles - metrics->mMetricsFields[ProcessCycles]; if (metrics->mMetricsItemsValid & (1<mMetricsFields[HeapInUsage] = heap_get_free_mem(); #ifdef DO_PEAKHEAPCHECK if (metrics->mMetricsItemsValid & (1<mMetricsFields[PeakHeapUsage] = heap_get_peak_mem(); #endif // DO_PEAKHEAPCHECK #ifdef DO_PEAKSTACKCHECK if (metrics->mMetricsItemsValid & (1<mMetricsFields[PeakStackUsage] = get_stack_usage("audio"); #endif // DO_PEAKSTACKCHECK for (uint32_t i = 0; i < kPerformanceSize; ++i) { if ((ReservedStart+i) >= MetricsFieldsSize) break; if (performanceDetails[i].valid) { metrics->mMetricsFields[ReservedStart+i] = performanceDetails[i].time; metrics->mMetricsItemsValid |= 1<<(ReservedStart+i); } } } #else void PerformanceSnapShotStart(uint32_t which) {} void PerformanceSnapShotEnd(uint32_t which) {} void SetupProfiling(IOPAUDIO_METRICS* metrics) {} void EndProfiling(IOPAUDIO_METRICS* metrics) { metrics->mMetricsItemsValid = 0; } #endif static bool audio_message_process(void); static int iop_audio_task(void *cfg); static void iop_audio_sleep(int mode); IOP_FUNCTION(audio, iop_audio_task, 64*1024, AUDIO_CONTROL_CHANNEL); IOP_SLEEP_HOOK(audio, iop_audio_sleep); static int audio_channel; #define kMaxCodecs 8 static AudioCodec sTokens[kMaxCodecs] = { NULL, }; // return true if registered, false if could not. static bool Register(AudioCodec audioCodec) { for (size_t i = 0; i < kMaxCodecs; ++i) if (!sTokens[i]) { sTokens[i] = audioCodec; return true; } return false; } static void Unregister(AudioCodec audioCodec) { for (size_t i = 0; i < kMaxCodecs; ++i) if (sTokens[i] == audioCodec) sTokens[i] = NULL; } static bool isAudioCodecValid(AudioCodec audioCodec) { for (size_t i = 0; i < kMaxCodecs; ++i) if (sTokens[i] == audioCodec) return true; return false; } static int iop_audio_task(void *cfg) { struct iop_channel_config *channel = (struct iop_channel_config *)cfg; struct task_event* audio_message_event = (struct task_event*) malloc(sizeof(struct task_event)); dprintf(DEBUG_SPEW, "@@ Audio task starting\n"); check(kIOPAUDIO_COMMAND_SIZE == sizeof(IOPAUDIO_Command)); /* establish the host communications channel */ event_init(audio_message_event, EVENT_FLAG_AUTO_UNSIGNAL, false); dprintf(DEBUG_SPEW, "** opening audio channel\n"); audio_channel = qwi_instantiate_channel("audio command", QWI_ROLE_CONSUMER, channel->ring_size, (void *)mem_static_map_cached(channel->ring_base), (qwi_channel_hook)event_signal, audio_message_event); #if WITH_VFP && !WITH_VFP_ALWAYS_ON /* Little doubt we'll need VFP/Neon */ arch_task_fp_enable(true); #endif for (;;) { dprintf(DEBUG_SPEW, "@@ waiting for message on audio channel\n"); while (audio_message_process()) { // eat all available messages } event_wait(audio_message_event); } return(0); } static bool audio_message_process(void) { uint32_t message; IOPAUDIO_Command* command; dprintf(DEBUG_SPEW, "@@ handling host message\n"); /* look to see if there's an item waiting for us */ if (qwi_receive_item(audio_channel, &message) == -1) return(false); // set the clocks high SetClockState(kClockRequestMessageProcess, kClockValueHigh); dprintf(DEBUG_SPEW, "@@ received audio message\n"); /* find the command structure based on the message */ command = (IOPAUDIO_Command*)mem_static_map_cached(message); /* * Flush any cached item contents we might have lying around - we are guaranteed * that the command size is a multiple of our cacheline size. */ platform_cache_operation(CACHE_INVALIDATE, (void *)command, sizeof(*command)); /* * TODO: make this part of the API and push this * architecture-specific command handling down into the s5l8920x * platform directory. */ switch (command->iopaudio.mOpcode) { case kIOPAUDIO_OPCODE_CREATE: { AudioComponent comp = NULL; AudioComponentDescription desc; desc.componentType = command->create.mComponentDesc.mComponentType; desc.componentSubType = command->create.mComponentDesc.mComponentSubType; desc.componentManufacturer = command->create.mComponentDesc.mComponentManufacturer; desc.componentFlags = command->create.mComponentDesc.mComponentFlags; desc.componentFlagsMask = command->create.mComponentDesc.mComponentFlagsMask; comp = AudioComponentFindNext(comp, &desc); if(comp == NULL) { dprintf(DEBUG_CRITICAL, "Could not find AudioComponent\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_CODECERROR; break; } dprintf(DEBUG_CRITICAL, "found component\n"); AudioCodec audioCodec = NULL; size_t err = AudioComponentInstanceNew(comp, &audioCodec); command->iopaudio.Codec.mCodecStatus = err; if (err != 0 || audioCodec == NULL) { dprintf(DEBUG_CRITICAL, "Could not create audioCodec\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_CODECERROR; break; } const void *magicCookie = (command->create.mAdditionalParametersSizeBytes) ? command->create.mAdditionalParameters : 0; err = AudioCodecInitialize(audioCodec, &(command->create.mInputFormat), &(command->create.mOutputFormat), magicCookie, command->create.mAdditionalParametersSizeBytes); command->iopaudio.Codec.mCodecStatus = err; if (err != 0) { dprintf(DEBUG_CRITICAL, "Could not initialize audioCodec\n"); AudioComponentInstanceDispose(audioCodec); command->iopaudio.mStatus = kIOPAUDIO_STATUS_CODECERROR; break; } if (!Register(audioCodec)) { dprintf(DEBUG_CRITICAL, "Could not register audioCodec\n"); AudioComponentInstanceDispose(audioCodec); command->iopaudio.mStatus = kIOPAUDIO_STATUS_CODECERROR; break; } command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; command->iopaudio.Codec.mIOPToken = (uint32_t)audioCodec; dprintf(DEBUG_CRITICAL, "Created codec with id %x\n", command->iopaudio.Codec.mIOPToken); } break; case kIOPAUDIO_OPCODE_DESTROY: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (!isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } AudioComponentInstanceDispose(audioCodec); Unregister(audioCodec); command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; case kIOPAUDIO_OPCODE_RESET: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (!isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } size_t err = AudioCodecReset(audioCodec); command->iopaudio.Codec.mCodecStatus = err; command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; case kIOPAUDIO_OPCODE_GETPROPINFO: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } size_t err = AudioCodecGetPropertyInfo(audioCodec, command->get_propinfo.mPropertyID, (size_t*)&command->get_propinfo.mPropertySize, (bool*)&command->get_propinfo.mPropertyWritable); command->iopaudio.Codec.mCodecStatus = err; command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; case kIOPAUDIO_OPCODE_GETPROPERTY: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } // update getProperty and setProperty to limit property address size_t max_size_for_property = sizeof(*command) - ((uint32_t)&command->get_property.mPropertyData - (uint32_t)command); if (max_size_for_property < command->get_property.mPropertySizeBytes) { dprintf(DEBUG_CRITICAL, "Parameters not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } // the message should be cleaned before and after we use it, so do not need to clean ourselves void* property = (void*)command->get_property.mPropertyData; size_t err = AudioCodecGetProperty(audioCodec, command->get_property.mPropertyID, (size_t*)&command->get_property.mPropertySizeBytes, property); command->iopaudio.Codec.mCodecStatus = err; command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; case kIOPAUDIO_OPCODE_SETPROPERTY: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } // update getProperty and setProperty to limit property address size_t max_size_for_property = sizeof(*command) - ((uint32_t)&command->get_property.mPropertyData - (uint32_t)command); if (max_size_for_property < command->get_property.mPropertySizeBytes) { dprintf(DEBUG_CRITICAL, "Parameters not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } // the message should be cleaned before and after we use it, so do not need to clean ourselves void* property = (void*)command->get_property.mPropertyData; size_t err = AudioCodecSetProperty(audioCodec, command->set_property.mPropertyID, command->set_property.mPropertySizeBytes, property); command->iopaudio.Codec.mCodecStatus = err; command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; case kIOPAUDIO_OPCODE_PROCESSFRAME: { AudioCodec audioCodec = (AudioCodec)command->iopaudio.Codec.mIOPToken; if (!isAudioCodecValid(audioCodec)) { dprintf(DEBUG_CRITICAL, "Codec not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } // function_audio should make sure parameters to processSingleFrame (input, output buffers) are valid // make sure the parameters are not-null. if (!command->process_frame.mSrcAddr || !command->process_frame.mSrcSizeBytes || !command->process_frame.mDstAddr || !command->process_frame.mDstSizeBytes) { dprintf(DEBUG_CRITICAL, "Parameters not valid\n"); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } const void* src = (const void*)mem_static_map_cached(command->process_frame.mSrcAddr); void* dst = (void*)mem_static_map_cached(command->process_frame.mDstAddr); // the onus is on the caller to make sure the cache is aligned void* lineBase = (void*)((uint32_t)src & ~(CPU_CACHELINE_SIZE-1)); size_t sizeToClean = ((((uint32_t)src & (CPU_CACHELINE_SIZE-1)) + command->process_frame.mSrcSizeBytes)/CPU_CACHELINE_SIZE + 1) * CPU_CACHELINE_SIZE; platform_cache_operation(CACHE_CLEAN | CACHE_INVALIDATE, lineBase, sizeToClean); SetupProfiling(&command->process_frame.mMetrics); void* packet_dst = dst; uint16_t numPackets = command->process_frame.mNumPackets; size_t remaining_dst_size = command->process_frame.mDstSizeBytes; size_t total_dst_size = 0; size_t total_src_size = 0; //sanity check, if the multi frame fields are not used if(0 == numPackets) { numPackets = 1; command->process_frame.mPacketSize[0] = command->process_frame.mSrcSizeBytes; } for(uint16_t i = 0; i < numPackets; ++i) { size_t src_size = command->process_frame.mPacketSize[i]; //make sure we dont go pass all of the valid input data if(total_src_size + src_size > command->process_frame.mSrcSizeBytes) break; size_t packet_dst_size = remaining_dst_size; //the current mp3 decoder doesn't take dst size that's bigger than the packet output size dprintf(DEBUG_SPEW, "IOP_Audio start %d of %d, input %p size %zu, output %p, size %zu\n", i+1, numPackets, src, src_size, packet_dst, packet_dst_size); size_t err = AudioCodecProcessSinglePacket(audioCodec, src, &src_size, packet_dst, &packet_dst_size); command->iopaudio.Codec.mCodecStatus = err; if(err) break; packet_dst += packet_dst_size; remaining_dst_size -= packet_dst_size; total_dst_size += packet_dst_size; src += src_size; total_src_size += src_size; //for encoding, write the size back to the msg command->process_frame.mPacketSize[i] = packet_dst_size; } command->process_frame.mSrcSizeBytes = total_src_size; command->process_frame.mDstSizeBytes = total_dst_size; //dprintf(DEBUG_CRITICAL, "Decoded/Encoded Total SrcSize %d, dstSize %d\n", command->process_frame.mSrcSizeBytes, command->process_frame.mDstSizeBytes); EndProfiling(&command->process_frame.mMetrics); // the onus is on the caller to make sure the cache is aligned lineBase = (void*)((uint32_t)dst & ~(CPU_CACHELINE_SIZE-1)); sizeToClean = ((((uint32_t)dst & (CPU_CACHELINE_SIZE-1)) + command->process_frame.mDstSizeBytes)/CPU_CACHELINE_SIZE + 1) * CPU_CACHELINE_SIZE; platform_cache_operation(CACHE_CLEAN, lineBase, sizeToClean); command->iopaudio.mStatus = kIOPAUDIO_STATUS_SUCCESS; } break; default: dprintf(DEBUG_CRITICAL, "@@ ERROR: unrecognised audio opcode 0x%x\n", command->iopaudio.mOpcode); command->iopaudio.mStatus = kIOPAUDIO_STATUS_PARAM_INVALID; break; } dprintf(DEBUG_SPEW, "@@ done processing audio message with status 0x%08x\n", command->iopaudio.mStatus); platform_cache_operation(CACHE_CLEAN, (void *)command, sizeof(IOPAUDIO_Command)); qwi_send_item(audio_channel, message); dprintf(DEBUG_SPEW, "@@ signaled completion of audio message to host\n"); // set the clocks low SetClockState(kClockRequestMessageProcess, kClockValueLow); return(true); } static void iop_audio_sleep(int mode) { }