summaryrefslogtreecommitdiffstats
path: root/apps/iap/iap-core.c
diff options
context:
space:
mode:
authorRalf Ertzinger <rockbox@camperquake.de>2013-06-22 10:08:23 +0100
committerFrank Gevaerts <frank@gevaerts.be>2013-11-10 18:41:24 +0100
commitb170c73f922e3457b923b4e7fcbec794a8885c77 (patch)
tree89fbdbd8c25af5101a29a1ede3b896332a4e205c /apps/iap/iap-core.c
parent500b137308a6ee5c2aba873734a8956d70472f56 (diff)
downloadrockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.gz
rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.bz2
rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.zip
Updated IAP commands.
Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012. They have been condensed into a single patch and some further additions by Andy Potter. Currently includes Authentication V2 support from iPod to Accessory, RF/BlueTooth transmitter support, selecting a playlist and selecting a track from the current playlist. Does not support uploading Album Art or podcasts. Has been tested on the following iPods, 4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and Video 5.5Gen. Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e Co-Authored by: Andy Potter <liveboxandy@gmail.com> Reviewed-on: http://gerrit.rockbox.org/533 Reviewed-by: Frank Gevaerts <frank@gevaerts.be>
Diffstat (limited to 'apps/iap/iap-core.c')
-rw-r--r--apps/iap/iap-core.c1392
1 files changed, 1392 insertions, 0 deletions
diff --git a/apps/iap/iap-core.c b/apps/iap/iap-core.c
new file mode 100644
index 0000000000..ddcb22853a
--- /dev/null
+++ b/apps/iap/iap-core.c
@@ -0,0 +1,1392 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "panic.h"
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "button.h"
+#include "config.h"
+#include "cpu.h"
+#include "system.h"
+#include "kernel.h"
+#include "thread.h"
+#include "serial.h"
+#include "appevents.h"
+#include "core_alloc.h"
+
+#include "playlist.h"
+#include "playback.h"
+#include "audio.h"
+#include "settings.h"
+#include "metadata.h"
+#include "sound.h"
+#include "action.h"
+#include "powermgmt.h"
+
+#include "tuner.h"
+#include "ipod_remote_tuner.h"
+
+
+/* MS_TO_TICKS converts a milisecond time period into the
+ * corresponding amount of ticks. If the time period cannot
+ * be accurately measured in ticks it will round up.
+ */
+#if (HZ>1000)
+#error "HZ is >1000, please fix MS_TO_TICKS"
+#endif
+#define MS_PER_HZ (1000/HZ)
+#define MS_TO_TICKS(x) (((x)+MS_PER_HZ-1)/MS_PER_HZ)
+/* IAP specifies a timeout of 25ms for traffic from a device to the iPod.
+ * Depending on HZ this cannot be accurately measured. Find out the next
+ * best thing.
+ */
+#define IAP_PKT_TIMEOUT (MS_TO_TICKS(25))
+
+/* Events in the iap_queue */
+#define IAP_EV_TICK (1) /* The regular task timeout */
+#define IAP_EV_MSG_RCVD (2) /* A complete message has been received from the device */
+#define IAP_EV_MALLOC (3) /* Allocate memory for the RX/TX buffers */
+
+static bool iap_started = false;
+static bool iap_setupflag = false, iap_running = false;
+/* This is set to true if a SYS_POWEROFF message is received,
+ * signalling impending power off
+ */
+static bool iap_shutdown = false;
+static struct timeout iap_task_tmo;
+
+unsigned long iap_remotebtn = 0;
+/* Used to make sure a button press is delivered to the processing
+ * backend. While this is !0, no new incoming messasges are processed.
+ * Counted down by remote_control_rx()
+ */
+int iap_repeatbtn = 0;
+/* Used to time out button down events in case we miss the button up event
+ * from the device somehow.
+ * If a device sends a button down event it's required to repeat that event
+ * every 30 to 100ms as long as the button is pressed, and send an explicit
+ * button up event if the button is released.
+ * In case the button up event is lost any down events will time out after
+ * ~200ms.
+ * iap_periodic() will count down this variable and reset all buttons if
+ * it reaches 0
+ */
+unsigned int iap_timeoutbtn = 0;
+bool iap_btnrepeat = false, iap_btnshuffle = false;
+
+static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)];
+static struct event_queue iap_queue;
+
+/* These are pointer used to manage a dynamically allocated buffer which
+ * will hold both the RX and TX side of things.
+ *
+ * iap_buffer_handle is the handle returned from core_alloc()
+ * iap_buffers points to the start of the complete buffer
+ *
+ * The buffer is partitioned as follows:
+ * - TX_BUFLEN+6 bytes for the TX buffer
+ * The 6 extra bytes are for the sync byte, the SOP byte, the length indicators
+ * (3 bytes) and the checksum byte.
+ * iap_txstart points to the beginning of the TX buffer
+ * iap_txpayload points to the beginning of the payload portion of the TX buffer
+ * iap_txnext points to the position where the next byte will be placed
+ *
+ * - RX_BUFLEN+2 bytes for the RX buffer
+ * The RX buffer can hold multiple packets at once, up to it's
+ * maximum capacity. Every packet consists of a two byte length
+ * indicator followed by the actual payload. The length indicator
+ * is two bytes for every length, even for packets with a length <256
+ * bytes.
+ *
+ * Once a packet has been processed from the RX buffer the rest
+ * of the buffer (and the pointers below) are shifted to the front
+ * so that the next packet again starts at the beginning of the
+ * buffer. This happens with interrupts disabled, to prevent
+ * writing into the buffer during the move.
+ *
+ * iap_rxstart points to the beginning of the RX buffer
+ * iap_rxpayload starts to the beginning of the currently recieved
+ * packet
+ * iap_rxnext points to the position where the next incoming byte
+ * will be placed
+ * iap_rxlen is not a pointer, but an indicator of the free
+ * space left in the RX buffer.
+ *
+ * The RX buffer is placed behind the TX buffer so that an eventual TX
+ * buffer overflow has some place to spill into where it will not cause
+ * immediate damage. See the comments for IAP_TX_* and iap_send_tx()
+ */
+#define IAP_MALLOC_SIZE (TX_BUFLEN+6+RX_BUFLEN+2)
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_buffer_handle;
+#endif
+static unsigned char* iap_buffers;
+static unsigned char* iap_rxstart;
+static unsigned char* iap_rxpayload;
+static unsigned char* iap_rxnext;
+static uint32_t iap_rxlen;
+static unsigned char* iap_txstart;
+unsigned char* iap_txpayload;
+unsigned char* iap_txnext;
+
+/* The versions of the various Lingoes we support. A major version
+ * of 0 means unsupported
+ */
+unsigned char lingo_versions[32][2] = {
+ {1, 9}, /* General lingo, 0x00 */
+ {0, 0}, /* Microphone lingo, 0x01, unsupported */
+ {1, 2}, /* Simple remote lingo, 0x02 */
+ {1, 5}, /* Display remote lingo, 0x03 */
+ {1, 12}, /* Extended Interface lingo, 0x04 */
+ {1, 1}, /* RF/BT Transmitter lingo, 0x05 */
+ {} /* All others are unsupported */
+};
+
+/* states of the iap de-framing state machine */
+enum fsm_state {
+ ST_SYNC, /* wait for 0xFF sync byte */
+ ST_SOF, /* wait for 0x55 start-of-frame byte */
+ ST_LEN, /* receive length byte (small packet) */
+ ST_LENH, /* receive length high byte (large packet) */
+ ST_LENL, /* receive length low byte (large packet) */
+ ST_DATA, /* receive data */
+ ST_CHECK /* verify checksum */
+};
+
+static struct state_t {
+ enum fsm_state state; /* current fsm state */
+ unsigned int len; /* payload data length */
+ unsigned int check; /* running checksum over [len,payload,check] */
+ unsigned int count; /* playload bytes counter */
+} frame_state = {
+ .state = ST_SYNC
+};
+
+enum interface_state interface_state = IST_STANDARD;
+
+struct device_t device;
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new);
+
+static struct buflib_callbacks iap_buflib_callbacks = {
+ iap_move_callback,
+ NULL
+};
+#endif
+
+static void iap_malloc(void);
+
+void put_u16(unsigned char *buf, const uint16_t data)
+{
+ buf[0] = (data >> 8) & 0xFF;
+ buf[1] = (data >> 0) & 0xFF;
+}
+
+void put_u32(unsigned char *buf, const uint32_t data)
+{
+ buf[0] = (data >> 24) & 0xFF;
+ buf[1] = (data >> 16) & 0xFF;
+ buf[2] = (data >> 8) & 0xFF;
+ buf[3] = (data >> 0) & 0xFF;
+}
+
+uint32_t get_u32(const unsigned char *buf)
+{
+ return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
+uint16_t get_u16(const unsigned char *buf)
+{
+ return (buf[0] << 8) | buf[1];
+}
+
+#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
+/* Convert a buffer into a printable string, perl style
+ * buf contains the data to be converted, len is the length
+ * of the buffer.
+ *
+ * This will convert at most 1024 bytes from buf
+ */
+static char* hexstring(const unsigned char *buf, unsigned int len) {
+ static char hexbuf[4097];
+ unsigned int l;
+ const unsigned char* p;
+ unsigned char* out;
+ unsigned char h[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ if (len > 1024) {
+ l = 1024;
+ } else {
+ l = len;
+ }
+ p = buf;
+ out = hexbuf;
+ do {
+ *out++ = h[(*p)>>4];
+ *out++ = h[*p & 0x0F];
+ } while(--l && p++);
+
+ *out = 0x00;
+
+ return hexbuf;
+}
+#endif
+
+
+void iap_tx_strlcpy(const unsigned char *str)
+{
+ ptrdiff_t txfree;
+ int r;
+
+ txfree = TX_BUFLEN - (iap_txnext - iap_txstart);
+ r = strlcpy(iap_txnext, str, txfree);
+
+ if (r < txfree)
+ {
+ /* No truncation occured
+ * Account for the terminating \0
+ */
+ iap_txnext += (r+1);
+ } else {
+ /* Truncation occured, the TX buffer is now full. */
+ iap_txnext = iap_txstart + TX_BUFLEN;
+ }
+}
+
+void iap_reset_auth(struct auth_t* auth)
+{
+ auth->state = AUST_NONE;
+ auth->max_section = 0;
+ auth->next_section = 0;
+}
+
+void iap_reset_device(struct device_t* device)
+{
+ iap_reset_auth(&(device->auth));
+ device->lingoes = 0;
+ device->notifications = 0;
+ device->changed_notifications = 0;
+ device->do_notify = false;
+ device->do_power_notify = false;
+ device->accinfo = ACCST_NONE;
+ device->capabilities = 0;
+ device->capabilities_queried = 0;
+}
+
+static int iap_task(struct timeout *tmo)
+{
+ (void) tmo;
+
+ queue_post(&iap_queue, IAP_EV_TICK, 0);
+ return MS_TO_TICKS(100);
+}
+
+/* This thread is waiting for events posted to iap_queue and calls
+ * the appropriate subroutines in response
+ */
+static void iap_thread(void)
+{
+ struct queue_event ev;
+ while(1) {
+ queue_wait(&iap_queue, &ev);
+ switch (ev.id)
+ {
+ /* Handle the regular 100ms tick used for driving the
+ * authentication state machine and notifications
+ */
+ case IAP_EV_TICK:
+ {
+ iap_periodic();
+ break;
+ }
+
+ /* Handle a newly received message from the device */
+ case IAP_EV_MSG_RCVD:
+ {
+ iap_handlepkt();
+ break;
+ }
+
+ /* Handle memory allocation. This is used only once, during
+ * startup
+ */
+ case IAP_EV_MALLOC:
+ {
+ iap_malloc();
+ break;
+ }
+
+ /* Handle poweroff message */
+ case SYS_POWEROFF:
+ {
+ iap_shutdown = true;
+ break;
+ }
+ }
+ }
+}
+
+/* called by playback when the next track starts */
+static void iap_track_changed(void *ignored)
+{
+ (void)ignored;
+ if ((interface_state == IST_EXTENDED) && device.do_notify) {
+ long playlist_pos = playlist_next(0);
+ playlist_pos -= playlist_get_first_index(NULL);
+ if(playlist_pos < 0)
+ playlist_pos += playlist_amount();
+
+ IAP_TX_INIT4(0x04, 0x0027);
+ IAP_TX_PUT(0x01);
+ IAP_TX_PUT_U32(playlist_pos);
+
+ iap_send_tx();
+ return;
+ }
+}
+
+/* Do general setup of the needed infrastructure.
+ *
+ * Please note that a lot of additional work is done by iap_start()
+ */
+void iap_setup(const int ratenum)
+{
+ iap_bitrate_set(ratenum);
+ iap_remotebtn = BUTTON_NONE;
+ iap_setupflag = true;
+ iap_started = false;
+ iap_running = false;
+}
+
+/* Actually bring up the message queue, message handler thread and
+ * notification timer
+ *
+ * NOTE: This is running in interrupt context
+ */
+static void iap_start(void)
+{
+ unsigned int tid;
+
+ if (iap_started)
+ return;
+
+ iap_reset_device(&device);
+ queue_init(&iap_queue, true);
+ tid = create_thread(iap_thread, thread_stack, sizeof(thread_stack),
+ 0, "iap"
+ IF_PRIO(, PRIORITY_SYSTEM)
+ IF_COP(, CPU));
+ if (!tid)
+ panicf("Could not create iap thread");
+ timeout_register(&iap_task_tmo, iap_task, MS_TO_TICKS(100), (intptr_t)NULL);
+ add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed);
+
+ /* Since we cannot allocate memory while in interrupt context
+ * post a message to our own queue to get that done
+ */
+ queue_post(&iap_queue, IAP_EV_MALLOC, 0);
+ iap_started = true;
+}
+
+static void iap_malloc(void)
+{
+#ifndef IAP_MALLOC_DYNAMIC
+ static unsigned char serbuf[IAP_MALLOC_SIZE];
+#endif
+
+ if (iap_running)
+ return;
+
+#ifdef IAP_MALLOC_DYNAMIC
+ iap_buffer_handle = core_alloc_ex("iap", IAP_MALLOC_SIZE, &iap_buflib_callbacks);
+ if (iap_buffer_handle < 0)
+ panicf("Could not allocate buffer memory");
+ iap_buffers = core_get_data(iap_buffer_handle);
+#else
+ iap_buffers = serbuf;
+#endif
+ iap_txstart = iap_buffers;
+ iap_txpayload = iap_txstart+5;
+ iap_txnext = iap_txpayload;
+ iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+ iap_rxpayload = iap_rxstart;
+ iap_rxnext = iap_rxpayload;
+ iap_rxlen = RX_BUFLEN+2;
+ iap_running = true;
+}
+
+void iap_bitrate_set(const int ratenum)
+{
+ switch(ratenum)
+ {
+ case 0:
+ serial_bitrate(0);
+ break;
+ case 1:
+ serial_bitrate(9600);
+ break;
+ case 2:
+ serial_bitrate(19200);
+ break;
+ case 3:
+ serial_bitrate(38400);
+ break;
+ case 4:
+ serial_bitrate(57600);
+ break;
+ }
+}
+
+/* Message format:
+ 0xff
+ 0x55
+ length
+ mode
+ command (2 bytes)
+ parameters (0-n bytes)
+ checksum (length+mode+parameters+checksum == 0)
+*/
+
+/* Send the current content of the TX buffer.
+ * This will check for TX buffer overflow and panic, but it might
+ * be too late by then (although one would have to overflow the complete
+ * RX buffer as well)
+ */
+void iap_send_tx(void)
+{
+ int i, chksum;
+ ptrdiff_t txlen;
+ unsigned char* txstart;
+
+ txlen = iap_txnext - iap_txpayload;
+
+ if (txlen <= 0)
+ return;
+
+ if (txlen > TX_BUFLEN)
+ panicf("IAP: TX buffer overflow");
+
+ if (txlen < 256)
+ {
+ /* Short packet */
+ txstart = iap_txstart+2;
+ *(txstart+2) = txlen;
+ chksum = txlen;
+ } else {
+ /* Long packet */
+ txstart = iap_txstart;
+ *(txstart+2) = 0x00;
+ *(txstart+3) = (txlen >> 8) & 0xFF;
+ *(txstart+4) = (txlen) & 0xFF;
+ chksum = *(txstart+3) + *(txstart+4);
+ }
+ *(txstart) = 0xFF;
+ *(txstart+1) = 0x55;
+
+ for (i=0; i<txlen; i++)
+ {
+ chksum += iap_txpayload[i];
+ }
+ *(iap_txnext) = 0x100 - (chksum & 0xFF);
+
+#ifdef LOGF_ENABLE
+ logf("T: %s", hexstring(txstart+3, (iap_txnext - txstart)-3));
+#endif
+ for (i=0; i <= (iap_txnext - txstart); i++)
+ {
+ while(!tx_rdy()) ;
+ tx_writec(txstart[i]);
+ }
+}
+
+/* This is just a compatibility wrapper around the new TX buffer
+ * infrastructure
+ */
+void iap_send_pkt(const unsigned char * data, const int len)
+{
+ if (!iap_running)
+ return;
+
+ iap_txnext = iap_txpayload;
+ IAP_TX_PUT_DATA(data, len);
+ iap_send_tx();
+}
+
+bool iap_getc(const unsigned char x)
+{
+ struct state_t *s = &frame_state;
+ static long pkt_timeout;
+
+ if (!iap_setupflag)
+ return false;
+
+ /* Check the time since the last packet arrived. */
+ if ((s->state != ST_SYNC) && TIME_AFTER(current_tick, pkt_timeout)) {
+ /* Packet timeouts only make sense while not waiting for the
+ * sync byte */
+ s->state = ST_SYNC;
+ return iap_getc(x);
+ }
+
+
+ /* run state machine to detect and extract a valid frame */
+ switch (s->state) {
+ case ST_SYNC:
+ if (x == 0xFF) {
+ /* The IAP infrastructure is started by the first received sync
+ * byte. It takes a while to spin up, so do not advance the state
+ * machine until it has started.
+ */
+ if (!iap_running)
+ {
+ iap_start();
+ break;
+ }
+ iap_rxnext = iap_rxpayload;
+ s->state = ST_SOF;
+ }
+ break;
+ case ST_SOF:
+ if (x == 0x55) {
+ /* received a valid sync/SOF pair */
+ s->state = ST_LEN;
+ } else {
+ s->state = ST_SYNC;
+ return iap_getc(x);
+ }
+ break;
+ case ST_LEN:
+ s->check = x;
+ s->count = 0;
+ if (x == 0) {
+ /* large packet */
+ s->state = ST_LENH;
+ } else {
+ /* small packet */
+ if (x > (iap_rxlen-2))
+ {
+ /* Packet too long for buffer */
+ s->state = ST_SYNC;
+ break;
+ }
+ s->len = x;
+ s->state = ST_DATA;
+ put_u16(iap_rxnext, s->len);
+ iap_rxnext += 2;
+ }
+ break;
+ case ST_LENH:
+ s->check += x;
+ s->len = x << 8;
+ s->state = ST_LENL;
+ break;
+ case ST_LENL:
+ s->check += x;
+ s->len += x;
+ if ((s->len == 0) || (s->len > (iap_rxlen-2))) {
+ /* invalid length */
+ s->state = ST_SYNC;
+ break;
+ } else {
+ s->state = ST_DATA;
+ put_u16(iap_rxnext, s->len);
+ iap_rxnext += 2;
+ }
+ break;
+ case ST_DATA:
+ s->check += x;
+ *(iap_rxnext++) = x;
+ s->count += 1;
+ if (s->count == s->len) {
+ s->state = ST_CHECK;
+ }
+ break;
+ case ST_CHECK:
+ s->check += x;
+ if ((s->check & 0xFF) == 0) {
+ /* done, received a valid frame */
+ iap_rxpayload = iap_rxnext;
+ queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+ } else {
+ /* Invalid frame */
+ }
+ s->state = ST_SYNC;
+ break;
+ default:
+#ifdef LOGF_ENABLE
+ logf("Unhandled iap state %d", (int) s->state);
+#else
+ panicf("Unhandled iap state %d", (int) s->state);
+#endif
+ break;
+ }
+
+ pkt_timeout = current_tick + IAP_PKT_TIMEOUT;
+
+ /* return true while still hunting for the sync and start-of-frame byte */
+ return (s->state == ST_SYNC) || (s->state == ST_SOF);
+}
+
+void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3)
+{
+ int tracknum;
+ int fd;
+ struct playlist_track_info info;
+
+ tracknum = track;
+
+ tracknum += playlist_get_first_index(NULL);
+ if(tracknum >= playlist_amount())
+ tracknum -= playlist_amount();
+
+ /* If the tracknumber is not the current one,
+ read id3 from disk */
+ if(playlist_next(0) != tracknum)
+ {
+ playlist_get_track_info(NULL, tracknum, &info);
+ fd = open(info.filename, O_RDONLY);
+ memset(id3, 0, sizeof(*id3));
+ get_metadata(id3, fd, info.filename);
+ close(fd);
+ } else {
+ memcpy(id3, audio_current_track(), sizeof(*id3));
+ }
+}
+
+uint32_t iap_get_trackpos(void)
+{
+ struct mp3entry *id3 = audio_current_track();
+
+ return id3->elapsed;
+}
+
+uint32_t iap_get_trackindex(void)
+{
+ struct playlist_info* playlist = playlist_get_current();
+
+ return (playlist->index - playlist->first_index);
+}
+
+void iap_periodic(void)
+{
+ static int count;
+
+ if(!iap_setupflag) return;
+
+ /* Handle pending authentication tasks */
+ switch (device.auth.state)
+ {
+ case AUST_INIT:
+ {
+ /* Send out GetDevAuthenticationInfo */
+ IAP_TX_INIT(0x00, 0x14);
+
+ iap_send_tx();
+ device.auth.state = AUST_CERTREQ;
+ break;
+ }
+
+ case AUST_CERTDONE:
+ {
+ /* Send out GetDevAuthenticationSignature, with
+ * 20 bytes of challenge and a retry counter of 1.
+ * Since we do not really care about the content of the
+ * challenge we just use the first 20 bytes of whatever
+ * is in the RX buffer right now.
+ */
+ IAP_TX_INIT(0x00, 0x17);
+ IAP_TX_PUT_DATA(iap_rxstart, 20);
+ IAP_TX_PUT(0x01);
+
+ iap_send_tx();
+ device.auth.state = AUST_CHASENT;
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ /* Time out button down events */
+ if (iap_timeoutbtn)
+ iap_timeoutbtn -= 1;
+
+ if (!iap_timeoutbtn)
+ {
+ iap_remotebtn = BUTTON_NONE;
+ iap_repeatbtn = 0;
+ iap_btnshuffle = false;
+ iap_btnrepeat = false;
+ }
+
+ /* Handle power down messages. */
+ if (iap_shutdown && device.do_power_notify)
+ {
+ /* NotifyiPodStateChange */
+ IAP_TX_INIT(0x00, 0x23);
+ IAP_TX_PUT(0x01);
+
+ iap_send_tx();
+
+ /* No further actions, we're going down */
+ iap_reset_device(&device);
+ return;
+ }
+
+ /* Handle GetAccessoryInfo messages */
+ if (device.accinfo == ACCST_INIT)
+ {
+ /* GetAccessoryInfo */
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ device.accinfo = ACCST_SENT;
+ }
+
+ /* Do not send requests for device information while
+ * an authentication is still running, this seems to
+ * confuse some devices
+ */
+ if (!DEVICE_AUTH_RUNNING && (device.accinfo == ACCST_DATA))
+ {
+ int first_set;
+
+ /* Find the first bit set in the capabilities field,
+ * ignoring those we already asked for
+ */
+ first_set = find_first_set_bit(device.capabilities & (~device.capabilities_queried));
+
+ if (first_set != 32)
+ {
+ /* Add bit to queried cababilities */
+ device.capabilities_queried |= BIT_N(first_set);
+
+ switch (first_set)
+ {
+ /* Name */
+ case 0x01:
+ /* Firmware version */
+ case 0x04:
+ /* Hardware version */
+ case 0x05:
+ /* Manufacturer */
+ case 0x06:
+ /* Model number */
+ case 0x07:
+ /* Serial number */
+ case 0x08:
+ /* Maximum payload size */
+ case 0x09:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(first_set);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* Minimum supported iPod firmware version */
+ case 0x02:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(2);
+ IAP_TX_PUT_U32(IAP_IPOD_MODEL);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* Minimum supported lingo version. Queries Lingo 0 */
+ case 0x03:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(3);
+ IAP_TX_PUT(0);
+
+ iap_send_tx();
+ break;
+ }
+ }
+
+ device.accinfo = ACCST_SENT;
+ }
+ }
+
+ if (!device.do_notify) return;
+ if (device.notifications == 0) return;
+
+ /* Volume change notifications are sent every 100ms */
+ if (device.notifications & (BIT_N(4) | BIT_N(16))) {
+ /* Currently we do not track volume changes, so this is
+ * never sent.
+ *
+ * TODO: Fix volume tracking
+ */
+ }
+
+ /* All other events are sent every 500ms */
+ count += 1;
+ if (count < 5) return;
+
+ count = 0;
+
+ /* RemoteEventNotification */
+
+ /* Mode 04 PlayStatusChangeNotification */
+ /* Are we in Extended Mode */
+ if (interface_state == IST_EXTENDED) {
+ /* Return Track Position */
+ struct mp3entry *id3 = audio_current_track();
+ unsigned long time_elapsed = id3->elapsed;
+ IAP_TX_INIT4(0x04, 0x0027);
+ IAP_TX_PUT(0x04);
+ IAP_TX_PUT_U32(time_elapsed);
+
+ iap_send_tx();
+ }
+
+ /* Track position (ms) or Track position (s) */
+ if (device.notifications & (BIT_N(0) | BIT_N(15)))
+ {
+ uint32_t t;
+ uint16_t ts;
+ bool changed;
+
+ t = iap_get_trackpos();
+ ts = (t / 1000) & 0xFFFF;
+
+ if ((device.notifications & BIT_N(0)) && (device.trackpos_ms != t))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT_U32(t);
+ device.changed_notifications |= BIT_N(0);
+ changed = true;
+
+ iap_send_tx();
+ }
+
+ if ((device.notifications & BIT_N(15)) && (device.trackpos_s != ts)) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x0F);
+ IAP_TX_PUT_U16(ts);
+ device.changed_notifications |= BIT_N(15);
+ changed = true;
+
+ iap_send_tx();
+ }
+
+ if (changed)
+ {
+ device.trackpos_ms = t;
+ device.trackpos_s = ts;
+ }
+ }
+
+ /* Track index */
+ if (device.notifications & BIT_N(1))
+ {
+ uint32_t index;
+
+ index = iap_get_trackindex();
+
+ if (device.track_index != index) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x01);
+ IAP_TX_PUT_U32(index);
+ device.changed_notifications |= BIT_N(1);
+
+ iap_send_tx();
+
+ device.track_index = index;
+ }
+ }
+
+ /* Chapter index */
+ if (device.notifications & BIT_N(2))
+ {
+ uint32_t index;
+
+ index = iap_get_trackindex();
+
+ if (device.track_index != index)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x02);
+ IAP_TX_PUT_U32(index);
+ IAP_TX_PUT_U16(0);
+ IAP_TX_PUT_U16(0xFFFF);
+ device.changed_notifications |= BIT_N(2);
+
+ iap_send_tx();
+
+ device.track_index = index;
+ }
+ }
+
+ /* Play status */
+ if (device.notifications & BIT_N(3))
+ {
+ unsigned char play_status;
+
+ play_status = audio_status();
+
+ if (device.play_status != play_status)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x03);
+ if (play_status & AUDIO_STATUS_PLAY) {
+ /* Playing or paused */
+ if (play_status & AUDIO_STATUS_PAUSE) {
+ /* Paused */
+ IAP_TX_PUT(0x02);
+ } else {
+ /* Playing */
+ IAP_TX_PUT(0x01);
+ }
+ } else {
+ IAP_TX_PUT(0x00);
+ }
+ device.changed_notifications |= BIT_N(3);
+
+ iap_send_tx();
+
+ device.play_status = play_status;
+ }
+ }
+
+ /* Power/Battery */
+ if (device.notifications & BIT_N(5))
+ {
+ unsigned char power_state;
+ unsigned char battery_l;
+
+ power_state = charger_input_state;
+ battery_l = battery_level();
+
+ if ((device.power_state != power_state) || (device.battery_level != battery_l))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x05);
+
+ iap_fill_power_state();
+ device.changed_notifications |= BIT_N(5);
+
+ iap_send_tx();
+
+ device.power_state = power_state;
+ device.battery_level = battery_l;
+ }
+ }
+
+ /* Equalizer state
+ * This is not handled yet.
+ *
+ * TODO: Fix equalizer handling
+ */
+
+ /* Shuffle */
+ if (device.notifications & BIT_N(7))
+ {
+ unsigned char shuffle;
+
+ shuffle = global_settings.playlist_shuffle;
+
+ if (device.shuffle != shuffle)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x07);
+ IAP_TX_PUT(shuffle?0x01:0x00);
+ device.changed_notifications |= BIT_N(7);
+
+ iap_send_tx();
+
+ device.shuffle = shuffle;
+ }
+ }
+
+ /* Repeat */
+ if (device.notifications & BIT_N(8))
+ {
+ unsigned char repeat;
+
+ repeat = global_settings.repeat_mode;
+
+ if (device.repeat != repeat)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x08);
+ switch (repeat)
+ {
+ case REPEAT_OFF:
+ {
+ IAP_TX_PUT(0x00);
+ break;
+ }
+
+ case REPEAT_ONE:
+ {
+ IAP_TX_PUT(0x01);
+ break;
+ }
+
+ case REPEAT_ALL:
+ {
+ IAP_TX_PUT(0x02);
+ break;
+ }
+ }
+ device.changed_notifications |= BIT_N(8);
+
+ iap_send_tx();
+
+ device.repeat = repeat;
+ }
+ }
+
+ /* Date/Time */
+ if (device.notifications & BIT_N(9))
+ {
+ struct tm* tm;
+
+ tm = get_time();
+
+ if (memcmp(tm, &(device.datetime), sizeof(struct tm)))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x09);
+ IAP_TX_PUT_U16(tm->tm_year);
+
+ /* Month */
+ IAP_TX_PUT(tm->tm_mon+1);
+
+ /* Day */
+ IAP_TX_PUT(tm->tm_mday);
+
+ /* Hour */
+ IAP_TX_PUT(tm->tm_hour);
+
+ /* Minute */
+ IAP_TX_PUT(tm->tm_min);
+
+ device.changed_notifications |= BIT_N(9);
+
+ iap_send_tx();
+
+ memcpy(&(device.datetime), tm, sizeof(struct tm));
+ }
+ }
+
+ /* Alarm
+ * This is not supported yet.
+ *
+ * TODO: Fix alarm handling
+ */
+
+ /* Backlight
+ * This is not supported yet.
+ *
+ * TODO: Fix backlight handling
+ */
+
+ /* Hold switch */
+ if (device.notifications & BIT_N(0x0C))
+ {
+ unsigned char hold;
+
+ hold = button_hold();
+ if (device.hold != hold) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x0C);
+ IAP_TX_PUT(hold?0x01:0x00);
+
+ device.changed_notifications |= BIT_N(0x0C);
+
+ iap_send_tx();
+
+ device.hold = hold;
+ }
+ }
+
+ /* Sound check
+ * This is not supported yet.
+ *
+ * TODO: Fix sound check handling
+ */
+
+ /* Audiobook check
+ * This is not supported yet.
+ *
+ * TODO: Fix audiobook handling
+ */
+}
+
+/* Change the current interface state.
+ * On a change from IST_EXTENDED to IST_STANDARD, or from IST_STANDARD
+ * to IST_EXTENDED, pause playback, if playing
+ */
+void iap_interface_state_change(const enum interface_state new)
+{
+ if (((interface_state == IST_EXTENDED) && (new == IST_STANDARD)) ||
+ ((interface_state == IST_STANDARD) && (new == IST_EXTENDED))) {
+ if (audio_status() == AUDIO_STATUS_PLAY)
+ {
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ }
+ }
+
+ interface_state = new;
+}
+
+static void iap_handlepkt_mode5(const unsigned int len, const unsigned char *buf)
+{
+ (void) len;
+ unsigned int cmd = buf[1];
+ switch (cmd)
+ {
+ /* Sent from iPod Begin Transmission */
+ case 0x02:
+ {
+ /* RF Transmitter: Begin High Power transmission */
+ unsigned char data0[] = {0x05, 0x02};
+ iap_send_pkt(data0, sizeof(data0));
+ break;
+ }
+
+ /* Sent from iPod End High Power Transmission */
+ case 0x03:
+ {
+ /* RF Transmitter: End High Power transmission */
+ unsigned char data1[] = {0x05, 0x03};
+ iap_send_pkt(data1, sizeof(data1));
+ break;
+ }
+ /* Return Version Number ?*/
+ case 0x04:
+ {
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+#if 0
+static void iap_handlepkt_mode7(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = buf[1];
+ switch (cmd)
+ {
+ /* RetTunerCaps */
+ case 0x02:
+ {
+ /* do nothing */
+
+ /* GetAccessoryInfo */
+ unsigned char data[] = {0x00, 0x27, 0x00};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+
+ /* RetTunerFreq */
+ case 0x0A:
+ /* fall through */
+ /* TunerSeekDone */
+ case 0x13:
+ {
+ rmt_tuner_freq(len, buf);
+ break;
+ }
+
+ /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/
+ case 0x21:
+ {
+ rmt_tuner_rds_data(len, buf);
+ break;
+ }
+ }
+}
+#endif
+
+void iap_handlepkt(void)
+{
+ int level;
+ int length;
+
+ if(!iap_setupflag) return;
+
+ /* if we are waiting for a remote button to go out,
+ delay the handling of the new packet */
+ if(iap_repeatbtn)
+ {
+ queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+ sleep(1);
+ return;
+ }
+
+ /* handle command by mode */
+ length = get_u16(iap_rxstart);
+#ifdef LOGF_ENABLE
+ logf("R: %s", hexstring(iap_rxstart+2, (length)));
+#endif
+
+ unsigned char mode = *(iap_rxstart+2);
+ switch (mode) {
+ case 0: iap_handlepkt_mode0(length, iap_rxstart+2); break;
+ case 2: iap_handlepkt_mode2(length, iap_rxstart+2); break;
+ case 3: iap_handlepkt_mode3(length, iap_rxstart+2); break;
+ case 4: iap_handlepkt_mode4(length, iap_rxstart+2); break;
+ case 5: iap_handlepkt_mode5(length, iap_rxstart+2); break;
+ /* case 7: iap_handlepkt_mode7(length, iap_rxstart+2); break; */
+ }
+
+ /* Remove the handled packet from the RX buffer
+ * This needs to be done with interrupts disabled, to make
+ * sure the buffer and the pointers into it are handled
+ * cleanly
+ */
+ level = disable_irq_save();
+ memmove(iap_rxstart, iap_rxstart+(length+2), (RX_BUFLEN+2)-(length+2));
+ iap_rxnext -= (length+2);
+ iap_rxpayload -= (length+2);
+ iap_rxlen += (length+2);
+ restore_irq(level);
+
+ /* poke the poweroff timer */
+ reset_poweroff_timer();
+}
+
+int remote_control_rx(void)
+{
+ int btn = iap_remotebtn;
+ if(iap_repeatbtn)
+ iap_repeatbtn--;
+
+ return btn;
+}
+
+const unsigned char *iap_get_serbuf(void)
+{
+ return iap_rxstart;
+}
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new)
+{
+ (void) handle;
+ (void) current;
+
+ iap_txstart = new;
+ iap_txpayload = iap_txstart+5;
+ iap_txnext = iap_txpayload;
+ iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+
+ return BUFLIB_CB_OK;
+}
+#endif
+
+/* Change the shuffle state */
+void iap_shuffle_state(const bool state)
+{
+ /* Set shuffle to enabled */
+ if(state && !global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 1;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_randomise(NULL, current_tick, true);
+ }
+ /* Set shuffle to disabled */
+ else if(!state && global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 0;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_sort(NULL, true);
+ }
+}
+
+/* Change the repeat state */
+void iap_repeat_state(const unsigned char state)
+{
+ if (state != global_settings.repeat_mode)
+ {
+ global_settings.repeat_mode = state;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ audio_flush_and_reload_tracks();
+ }
+}
+
+void iap_repeat_next(void)
+{
+ switch (global_settings.repeat_mode)
+ {
+ case REPEAT_OFF:
+ {
+ iap_repeat_state(REPEAT_ALL);
+ break;
+ }
+ case REPEAT_ALL:
+ {
+ iap_repeat_state(REPEAT_ONE);
+ break;
+ }
+ case REPEAT_ONE:
+ {
+ iap_repeat_state(REPEAT_OFF);
+ break;
+ }
+ }
+}
+
+/* This function puts the current power/battery state
+ * into the TX buffer. The buffer is assumed to be initialized
+ */
+void iap_fill_power_state(void)
+{
+ unsigned char power_state;
+ unsigned char battery_l;
+
+ power_state = charger_input_state;
+ battery_l = battery_level();
+
+ if (power_state == NO_CHARGER) {
+ if (battery_l < 30) {
+ IAP_TX_PUT(0x00);
+ } else {
+ IAP_TX_PUT(0x01);
+ }
+ IAP_TX_PUT((char)((battery_l * 255)/100));
+ } else {
+ IAP_TX_PUT(0x04);
+ IAP_TX_PUT(0x00);
+ }
+}