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 "UsbAlsaMidiDeviceJNI"
18 #define LOG_NDEBUG 0
19 #include <asm/byteorder.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <nativehelper/JNIPlatformHelp.h>
23 #include <nativehelper/ScopedLocalRef.h>
24 #include <sound/asound.h>
25 #include <stdio.h>
26 #include <sys/ioctl.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 
30 #include "android_runtime/AndroidRuntime.h"
31 #include "android_runtime/Log.h"
32 #include "jni.h"
33 #include "utils/Log.h"
34 
35 namespace android {
36 
37 static jclass sFileDescriptorClass;
38 static jfieldID sPipeFDField;
39 
40 // This function returns an array of integers, each representing a file descriptor.
41 // The will be in the order of inputs then outputs.
42 // The last input fd will be for a file descriptor that simply allows Os.poll() to keep working.
43 // For example, if numInputs is 2 and numOutputs is 1, the resulting fds are as follows:
44 // 1. Input O_RDONLY file descriptor
45 // 2. Special input file descriptor to block the input thread
46 // 3. Output O_WRONLY file descriptor
android_server_UsbAlsaMidiDevice_open(JNIEnv * env,jobject thiz,jint card,jint device,jint numInputs,jint numOutputs)47 static jobjectArray android_server_UsbAlsaMidiDevice_open(JNIEnv *env, jobject thiz, jint card,
48                                                           jint device, jint numInputs,
49                                                           jint numOutputs) {
50     char path[100];
51     int fd;
52 
53     snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
54 
55     ALOGD("Opening %d inputs and %d outputs", numInputs, numOutputs);
56 
57     jobjectArray fds = env->NewObjectArray(numInputs + numOutputs, sFileDescriptorClass, NULL);
58     if (!fds) {
59         return NULL;
60     }
61 
62     // open the path for the read pipes. The last one is special and used to
63     // unblock Os.poll()
64     for (int i = 0; i < numInputs - 1; i++) {
65         fd = open(path, O_RDONLY);
66         if (fd < 0) {
67             ALOGE("open failed on %s for index %d", path, i);
68             goto release_fds;
69         }
70         ScopedLocalRef<jobject> jifd(env, jniCreateFileDescriptor(env, fd));
71         if (jifd.get() == NULL) {
72             close(fd);
73             goto release_fds;
74         }
75         env->SetObjectArrayElement(fds, i, jifd.get());
76     }
77 
78     // open the path for the write pipes
79     for (int i = 0; i < numOutputs; i++) {
80         fd = open(path, O_WRONLY);
81         if (fd < 0) {
82             ALOGE("open failed on %s for index %d", path, i);
83             goto release_fds;
84         }
85         ScopedLocalRef<jobject> jifd(env, jniCreateFileDescriptor(env, fd));
86         if (jifd.get() == NULL) {
87             close(fd);
88             goto release_fds;
89         }
90         env->SetObjectArrayElement(fds, i + numInputs, jifd.get());
91     }
92 
93     // create a pipe to use for unblocking our input thread. The caller should
94     // set numInputs as 0 when there are zero real input threads.
95     if (numInputs > 0) {
96         int pipeFD[2];
97         if (pipe(pipeFD) == -1) {
98             ALOGE("pipe() failed, errno = %d", errno);
99             goto release_fds;
100         }
101 
102         ScopedLocalRef<jobject> jifd(env, jniCreateFileDescriptor(env, pipeFD[0]));
103         if (jifd.get() == NULL) {
104             close(pipeFD[0]);
105             close(pipeFD[1]);
106             goto release_fds;
107         }
108 
109         // store as last input file descriptor
110         env->SetObjectArrayElement(fds, numInputs - 1, jifd.get());
111         // store our end of the pipe in mPipeFD
112         env->SetIntField(thiz, sPipeFDField, pipeFD[1]);
113     }
114     return fds;
115 
116 release_fds:
117     for (int i = 0; i < numInputs + numOutputs; ++i) {
118         ScopedLocalRef<jobject> jifd(env, env->GetObjectArrayElement(fds, i));
119         if (jifd.get() != NULL) {
120             int fd = jniGetFDFromFileDescriptor(env, jifd.get());
121             close(fd);
122         }
123     }
124     return NULL;
125 }
126 
android_server_UsbAlsaMidiDevice_close(JNIEnv * env,jobject thiz,jobjectArray fds)127 static void android_server_UsbAlsaMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) {
128     // write to mPipeFD to unblock input thread
129     jint pipeFD = env->GetIntField(thiz, sPipeFDField);
130     write(pipeFD, &pipeFD, sizeof(pipeFD));
131     close(pipeFD);
132     env->SetIntField(thiz, sPipeFDField, -1);
133 
134     int count = env->GetArrayLength(fds);
135     for (int i = 0; i < count; i++) {
136         jobject fd = env->GetObjectArrayElement(fds, i);
137         close(jniGetFDFromFileDescriptor(env, fd));
138     }
139 }
140 
141 static JNINativeMethod method_table[] = {
142         {"nativeOpen", "(IIII)[Ljava/io/FileDescriptor;",
143          (void *)android_server_UsbAlsaMidiDevice_open},
144         {"nativeClose", "([Ljava/io/FileDescriptor;)V",
145          (void *)android_server_UsbAlsaMidiDevice_close},
146 };
147 
register_android_server_UsbAlsaMidiDevice(JNIEnv * env)148 int register_android_server_UsbAlsaMidiDevice(JNIEnv *env) {
149     jclass clazz = env->FindClass("java/io/FileDescriptor");
150     if (clazz == NULL) {
151         ALOGE("Can't find java/io/FileDescriptor");
152         return -1;
153     }
154     sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);
155 
156     clazz = env->FindClass("com/android/server/usb/UsbAlsaMidiDevice");
157     if (clazz == NULL) {
158         ALOGE("Can't find com/android/server/usb/UsbAlsaMidiDevice");
159         return -1;
160     }
161     sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I");
162     if (sPipeFDField == NULL) {
163         ALOGE("Can't find UsbAlsaMidiDevice.mPipeFD");
164         return -1;
165     }
166 
167     return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaMidiDevice", method_table,
168                                     NELEM(method_table));
169 }
170 
171 }; // namespace android
172