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 35 namespace android 36 { 37 38 static jclass sFileDescriptorClass; 39 static jfieldID sPipeFDField; 40 41 static jint 42 android_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 83 static jobjectArray 84 android_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 122 static void 123 android_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 138 static 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 144 int 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