1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "UsbMidiDeviceJNI"
18#define LOG_NDEBUG 0
19#include "utils/Log.h"
20
21#include "jni.h"
22#include "JNIHelp.h"
23#include "android_runtime/AndroidRuntime.h"
24#include "android_runtime/Log.h"
25
26#include <stdio.h>
27#include <errno.h>
28#include <asm/byteorder.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <fcntl.h>
32#include <sys/ioctl.h>
33#include <sound/asound.h>
34
35namespace android
36{
37
38static jclass sFileDescriptorClass;
39static jfieldID sPipeFDField;
40
41static jint
42android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
43        jint card, jint device)
44{
45    char    path[100];
46    int     fd;
47    const   int kMaxRetries = 10;
48    const   int kSleepMicroseconds = 2000;
49
50    snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
51    // This control device may not have been created yet. So we should
52    // try to open it several times to prevent intermittent failure
53    // from a race condition.
54    int retryCounter = 0;
55    while ((fd = open(path, O_RDWR)) < 0) {
56        if (++retryCounter > kMaxRetries) {
57            ALOGE("timed out after %d tries, could not open %s", retryCounter, path);
58            return 0;
59        } else {
60            ALOGW("attempt #%d, could not open %s", retryCounter, path);
61            // Increase the sleep interval each time.
62            // 10 retries will total 2 * sum(1..10) = 110 milliseconds.
63            // Typically the device should be ready in 5-10 milliseconds.
64            usleep(kSleepMicroseconds * retryCounter);
65        }
66    }
67
68    struct snd_rawmidi_info info;
69    memset(&info, 0, sizeof(info));
70    info.device = device;
71    int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
72    close(fd);
73
74    if (ret < 0) {
75        ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
76        return -1;
77    }
78
79    ALOGD("subdevices_count: %d", info.subdevices_count);
80    return info.subdevices_count;
81}
82
83static jobjectArray
84android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint device,
85        jint subdevice_count)
86{
87    char    path[100];
88
89    snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
90
91    // allocate one extra file descriptor for close pipe
92    jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL);
93    if (!fds) {
94        return NULL;
95    }
96
97    // to support multiple subdevices we open the same file multiple times
98    for (int i = 0; i < subdevice_count; i++) {
99        int fd = open(path, O_RDWR);
100        if (fd < 0) {
101            ALOGE("open failed on %s for index %d", path, i);
102            return NULL;
103        }
104
105        jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
106        env->SetObjectArrayElement(fds, i, fileDescriptor);
107        env->DeleteLocalRef(fileDescriptor);
108    }
109
110    // create a pipe to use for unblocking our input thread
111    int pipeFD[2];
112    pipe(pipeFD);
113    jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]);
114    env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor);
115    env->DeleteLocalRef(fileDescriptor);
116    // store our end of the pipe in mPipeFD
117    env->SetIntField(thiz, sPipeFDField, pipeFD[1]);
118
119    return fds;
120}
121
122static void
123android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds)
124{
125    // write to mPipeFD to unblock input thread
126    jint pipeFD = env->GetIntField(thiz, sPipeFDField);
127    write(pipeFD, &pipeFD, sizeof(pipeFD));
128    close(pipeFD);
129    env->SetIntField(thiz, sPipeFDField, -1);
130
131    int count = env->GetArrayLength(fds);
132    for (int i = 0; i < count; i++) {
133        jobject fd = env->GetObjectArrayElement(fds, i);
134        close(jniGetFDFromFileDescriptor(env, fd));
135    }
136}
137
138static JNINativeMethod method_table[] = {
139    { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
140    { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
141    { "nativeClose", "([Ljava/io/FileDescriptor;)V", (void*)android_server_UsbMidiDevice_close },
142};
143
144int register_android_server_UsbMidiDevice(JNIEnv *env)
145{
146    jclass clazz = env->FindClass("java/io/FileDescriptor");
147    if (clazz == NULL) {
148        ALOGE("Can't find java/io/FileDescriptor");
149        return -1;
150    }
151    sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);
152
153    clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
154    if (clazz == NULL) {
155        ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
156        return -1;
157    }
158    sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I");
159    if (sPipeFDField == NULL) {
160        ALOGE("Can't find UsbMidiDevice.mPipeFD");
161        return -1;
162    }
163
164    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
165            method_table, NELEM(method_table));
166}
167
168};
169