1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "hdf_audio_pnp_uevent_hdmi.h"
17 #include <asm/types.h>
18 #include <pthread.h>
19 #include <string.h>
20 #include <sys/epoll.h>
21 #include <sys/socket.h>
22 #include <sys/un.h>
23 #include <linux/if_packet.h>
24 #include <linux/netlink.h>
25 #include <netinet/in.h>
26 #include <net/if.h>
27 #include <unistd.h>
28 #include "audio_uhdf_log.h"
29 #include "hdf_audio_pnp_server.h"
30 #include "hdf_base.h"
31 #include "hdf_io_service.h"
32 #include "osal_time.h"
33 #include "securec.h"
34 
35 #define HDF_LOG_TAG HDF_AUDIO_HAL_HOST
36 
37 #define RECV_BUFFER_SIZE       2048
38 #define FILE_BUFFER_SIZE       7
39 #define UEVENT_STATE           "STATE"
40 #define UEVENT_HDMI_STATE      "HDMI="
41 #define UEVENT_HDMI_STATE_PLUG "HDMI=1"
42 #define UEVENT_HDMI_STATE_REMV "HDMI=0"
43 
44 #define HDMI_STATUS_FILE_PATH "/sys/class/extcon/extcon2/state"
45 
46 #define UEVENT_SOCKET_GROUPS    0xffffffff
47 
48 #define MAXEVENTS 1
49 #define TIMEOUT   (-1)
50 
51 #define AUDIO_HDMI_CARD_NAME "hdf_audio_codec_hdmi_dev0"
52 
AudioHdmiPnpUeventStatus(const char * statusStr,bool isPnp)53 static int32_t AudioHdmiPnpUeventStatus(const char *statusStr, bool isPnp)
54 {
55     if (statusStr == NULL) {
56         AUDIO_FUNC_LOGE("error statusStr is null");
57         return HDF_ERR_INVALID_PARAM;
58     }
59 
60     struct AudioEvent audioEvent;
61     if (strncmp(statusStr, UEVENT_HDMI_STATE_PLUG, strlen(UEVENT_HDMI_STATE_PLUG)) == 0) {
62         audioEvent.eventType = AUDIO_DEVICE_ADD;
63         if (AudioUhdfLoadDriver(AUDIO_HDMI_CARD_NAME) != HDF_SUCCESS) {
64             AUDIO_FUNC_LOGW("AudioUhdfLoadDriver Failed");
65         }
66         AUDIO_FUNC_LOGI("An HDMI device is plugged in");
67     } else if (strncmp(statusStr, UEVENT_HDMI_STATE_REMV, strlen(UEVENT_HDMI_STATE_REMV)) == 0) {
68         audioEvent.eventType = AUDIO_DEVICE_REMOVE;
69         if (AudioUhdfUnloadDriver(AUDIO_HDMI_CARD_NAME) != HDF_SUCCESS) {
70             AUDIO_FUNC_LOGW("AudioUhdfUnloadDriver Failed");
71         }
72         AUDIO_FUNC_LOGI("The HDMI device is removed");
73     } else {
74         AUDIO_FUNC_LOGE("error HDMI status unknown! statusStr = %{public}s", statusStr);
75         return HDF_FAILURE;
76     }
77 
78     audioEvent.deviceType = AUDIO_HDMI_DEVICE;
79     if (isPnp) {
80         return AudioPnpUpdateInfoOnly(audioEvent);
81     }
82 
83     return HDF_SUCCESS;
84 }
85 
AudioPnpUeventParse(const char * str)86 static int32_t AudioPnpUeventParse(const char *str)
87 {
88     if (str == NULL) {
89         AUDIO_FUNC_LOGE("error device is null");
90         return HDF_FAILURE;
91     }
92 
93     while (*str != '\0') {
94         if (strncmp(str, UEVENT_STATE, strlen(UEVENT_STATE)) == 0) {
95             const char *temp = str + strlen(UEVENT_STATE) + 1; // 1 is a skip character '='
96             if (strncmp(temp, UEVENT_HDMI_STATE, strlen(UEVENT_HDMI_STATE)) == 0) {
97                 return AudioHdmiPnpUeventStatus(temp, true);
98             }
99         }
100         str += strlen(str) + 1; // 1 is a skip character '\0'
101     }
102 
103     return HDF_SUCCESS;
104 }
105 
AudioHdmiOpenEventPoll(int32_t * sockFd,int * fdEpoll)106 static int32_t AudioHdmiOpenEventPoll(int32_t *sockFd, int *fdEpoll)
107 {
108     if (sockFd == NULL || fdEpoll == NULL) {
109         AUDIO_FUNC_LOGE("sockFd or fdEpoll is null");
110         return HDF_FAILURE;
111     }
112     struct sockaddr_nl snl;
113     struct epoll_event epollUdev;
114     int32_t buffSize = RECV_BUFFER_SIZE;
115 
116     snl.nl_family = AF_NETLINK;
117     snl.nl_groups = UEVENT_SOCKET_GROUPS;
118 
119     *fdEpoll = epoll_create1(EPOLL_CLOEXEC);
120     if (*fdEpoll < 0) {
121         AUDIO_FUNC_LOGE("error creating epoll fd: %{public}m");
122         return HDF_FAILURE;
123     }
124 
125     OsalMSleep(30); // Wait 30ms to resolve the conflict with the pnp uevent "address already in use"
126     *sockFd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
127     if (*sockFd < 0) {
128         AUDIO_FUNC_LOGE("new socket failed, %{public}d", errno);
129         close(*fdEpoll);
130         return HDF_FAILURE;
131     }
132 
133     if (setsockopt(*sockFd, SOL_SOCKET, SO_RCVBUF, &buffSize, sizeof(buffSize)) != 0) {
134         AUDIO_FUNC_LOGE("setsockopt failed %{public}m");
135         close(*fdEpoll);
136         close(*sockFd);
137         return HDF_FAILURE;
138     }
139 
140     if (bind(*sockFd, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl)) < 0) {
141         AUDIO_FUNC_LOGE("bind failed: %{public}m");
142         close(*fdEpoll);
143         close(*sockFd);
144         return HDF_FAILURE;
145     }
146 
147     (void)memset_s(&epollUdev, sizeof(struct epoll_event), 0, sizeof(struct epoll_event));
148     epollUdev.events = EPOLLIN;
149     epollUdev.data.fd = *sockFd;
150     if (epoll_ctl(*fdEpoll, EPOLL_CTL_ADD, *sockFd, &epollUdev) < 0) {
151         AUDIO_FUNC_LOGE("fail to add fd to epoll: %{public}m");
152         close(*fdEpoll);
153         close(*sockFd);
154         return HDF_FAILURE;
155     }
156 
157     return HDF_SUCCESS;
158 }
159 
InitializeHdmiStateInternal(void)160 static int32_t InitializeHdmiStateInternal(void)
161 {
162     char buffer[FILE_BUFFER_SIZE] = {0};
163 
164     FILE *pFile = fopen(HDMI_STATUS_FILE_PATH, "r");
165     if (pFile == NULL) {
166         AUDIO_FUNC_LOGE("open hdmi status file failed!");
167         return HDF_FAILURE;
168     }
169 
170     size_t length = fread(buffer, 1, FILE_BUFFER_SIZE, pFile);
171     if (length != FILE_BUFFER_SIZE) {
172         (void)fclose(pFile);
173         AUDIO_FUNC_LOGE("fread hdmi status file failed!%{public}zu", length);
174         return HDF_FAILURE;
175     }
176 
177     (void)fclose(pFile);
178     return AudioHdmiPnpUeventStatus(buffer, false);
179 }
180 
181 static bool g_hdmiPnpThreadRunning = false;
AudioHdmiPnpUeventStart(void * useless)182 static void AudioHdmiPnpUeventStart(void *useless)
183 {
184     (void)useless;
185 
186     int fdEpoll = -1;
187     int32_t sockFd = -1;
188 
189     AUDIO_FUNC_LOGI("audio hdmi uevent start!");
190     if (InitializeHdmiStateInternal() != HDF_SUCCESS) {
191         AUDIO_FUNC_LOGW("booting check hdmi audio device statu failed!");
192     }
193 
194     if (AudioHdmiOpenEventPoll(&sockFd, &fdEpoll) != HDF_SUCCESS) {
195         AUDIO_FUNC_LOGE("fail to open event poll");
196         return;
197     }
198 
199     while (g_hdmiPnpThreadRunning) {
200         struct epoll_event ev;
201         char buf[RECV_BUFFER_SIZE];
202 
203         if (epoll_wait(fdEpoll, &ev, MAXEVENTS, TIMEOUT) < 0) {
204             AUDIO_FUNC_LOGW("error receiving uevent message: %{public}m");
205             continue;
206         }
207 
208         (void)memset_s(buf, RECV_BUFFER_SIZE, 0, RECV_BUFFER_SIZE);
209 
210         (void)recv(sockFd, buf, RECV_BUFFER_SIZE, 0);
211 
212         if (AudioPnpUeventParse(buf) != HDF_SUCCESS) {
213             AUDIO_FUNC_LOGE("AudioPnpUeventParse failed");
214         }
215     }
216     close(fdEpoll);
217     close(sockFd);
218 
219     return;
220 }
221 
AudioHdmiPnpUeventStartThread(void)222 int32_t AudioHdmiPnpUeventStartThread(void)
223 {
224     const char *threadName = "pnp_hdmi";
225     g_hdmiPnpThreadRunning = true;
226 
227     AUDIO_FUNC_LOGI("create audio hdmi pnp uevent thread");
228     FfrtTaskAttr attr;
229     FfrtAttrInitFunc()(&attr);
230     FfrtAttrSetQosFunc()(&attr, FFRT_QOS_DEFAULT);
231     FfrtAttrSetNameFunc()(&attr, threadName);
232     FfrtSubmitBaseFunc()(FfrtCreateFunctionWrapper(AudioHdmiPnpUeventStart, NULL, NULL), NULL, NULL, &attr);
233 
234     return HDF_SUCCESS;
235 }
236 
AudioHdmiPnpUeventStopThread(void)237 void AudioHdmiPnpUeventStopThread(void)
238 {
239     AUDIO_FUNC_LOGI("audio hdmi pnp uevent thread exit");
240     g_hdmiPnpThreadRunning = false;
241 }
242