haptic: apply next HAL architecture (hal api + backend) 89/251389/2
authorYunmi Ha <yunmi.ha@samsung.com>
Wed, 13 Jan 2021 08:58:02 +0000 (17:58 +0900)
committerYunmi Ha <yunmi.ha@samsung.com>
Wed, 13 Jan 2021 08:59:30 +0000 (17:59 +0900)
Change-Id: Ifd45a29a393eec596d61ab9ca0359b56c0d2842d
Signed-off-by: Yunmi Ha <yunmi.ha@samsung.com>
CMakeLists.txt
hw/common/common.h [new file with mode: 0644]
hw/haptic/CMakeLists.txt [new file with mode: 0644]
hw/haptic/standard.c [new file with mode: 0644]
packaging/device-manager-plugin-tw3.spec

index 78b0366..056de09 100644 (file)
@@ -15,3 +15,4 @@ ADD_SUBDIRECTORY(hw/usb_cfs_client)
 ADD_SUBDIRECTORY(hw/thermal)
 ADD_SUBDIRECTORY(hw/bezel)
 ADD_SUBDIRECTORY(hw/touchsensitivity)
+ADD_SUBDIRECTORY(hw/haptic)
diff --git a/hw/common/common.h b/hw/common/common.h
new file mode 100644 (file)
index 0000000..3c506ff
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __HAL_BACKEND_COMMON_H__
+#define __HAL_BACKEND_COMMON_H__
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef FEATURE_DLOG
+    #define LOG_TAG "HALBACKEND_DEVICE"
+    #include <dlog.h>
+    #define _D(fmt, args...)    SLOGD(fmt, ##args)
+    #define _I(fmt, args...)    SLOGI(fmt, ##args)
+    #define _W(fmt, args...)    SLOGW(fmt, ##args)
+    #define _E(fmt, args...)    SLOGE(fmt, ##args)
+#else
+    #define _D(x, ...)
+    #define _I(x, ...)
+    #define _W(x, ...)
+    #define _E(x, ...)
+#endif
+
+#define EXPORT __attribute__ ((visibility("default")))
+
+#define ARRAY_SIZE(name) (sizeof(name)/sizeof(name[0]))
+
+#define SHARED_H_BUF_MAX 255
+
+static inline int sys_read_buf(char *file, char *buf, int len)
+{
+       int fd, r;
+
+       if (!file || !buf || len < 0)
+               return -EINVAL;
+
+       fd = open(file, O_RDONLY);
+       if (fd == -1)
+               return -ENOENT;
+
+       r = read(fd, buf, len);
+       close(fd);
+       if ((r >= 0) && (r < len))
+               buf[r] = '\0';
+       else
+               return -EIO;
+
+       return 0;
+}
+
+static inline int sys_write_buf(char *file, char *buf)
+{
+       int fd, r;
+
+       if (!file || !buf)
+               return -EINVAL;
+
+       fd = open(file, O_WRONLY);
+       if (fd == -1)
+               return -EPERM;
+
+       r = write(fd, buf, strlen(buf));
+       close(fd);
+       if (r < 0)
+               return -EIO;
+
+       return 0;
+}
+
+static inline int sys_get_int(char *fname, int *val)
+{
+       char buf[SHARED_H_BUF_MAX];
+       int r;
+
+       if (!fname || !val)
+               return -EINVAL;
+
+       r = sys_read_buf(fname, buf, sizeof(buf));
+       if (r < 0)
+               return r;
+
+       *val = atoi(buf);
+       return 0;
+}
+
+static inline int sys_get_str(char *fname, char *str, int len)
+{
+       int r;
+
+       if (!fname || !str || len < 0)
+               return -EINVAL;
+
+       r = sys_read_buf(fname, str, len);
+       if (r < 0)
+               return r;
+
+       return 0;
+}
+
+static inline int sys_set_int(char *fname, int val)
+{
+       char buf[SHARED_H_BUF_MAX];
+       int r;
+
+       if (!fname)
+               return -EINVAL;
+
+       snprintf(buf, sizeof(buf), "%d", val);
+       r = sys_write_buf(fname, buf);
+       if (r < 0)
+               return r;
+
+       return 0;
+}
+
+static inline int sys_set_str(char *fname, char *val)
+{
+       int r;
+
+       if (!fname || !val)
+               return -EINVAL;
+
+       r = sys_write_buf(fname, val);
+       if (r < 0)
+               return r;
+
+       return 0;
+}
+
+#endif /* __HAL_BACKEND_COMMON_H__ */
diff --git a/hw/haptic/CMakeLists.txt b/hw/haptic/CMakeLists.txt
new file mode 100644 (file)
index 0000000..23a7a50
--- /dev/null
@@ -0,0 +1,24 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+PROJECT(hal-backend-device-haptic C)
+
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+
+INCLUDE_DIRECTORIES(../common)
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(haptic_pkgs REQUIRED
+               dlog
+               glib-2.0
+               libsyscommon)
+
+FOREACH(flag ${haptic_pkgs_CFLAGS})
+       SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
+
+ADD_LIBRARY(${PROJECT_NAME} MODULE standard.c)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${haptic_pkgs_LDFLAGS})
+
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION /hal/lib COMPONENT RuntimeLibraries)
diff --git a/hw/haptic/standard.c b/hw/haptic/standard.c
new file mode 100644 (file)
index 0000000..d30b12b
--- /dev/null
@@ -0,0 +1,587 @@
+/*
+ * hal-backend-device-haptic
+ *
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <linux/input.h>
+#include <libsyscommon/list.h>
+#include <hal/device/hal-haptic-interface.h>
+
+#include "common.h"
+
+#define MAX_MAGNITUDE                  0xFFFF
+#define PERIODIC_MAX_MAGNITUDE 0x7FFF  /* 0.5 * MAX_MAGNITUDE */
+#define RUMBLE_MAX_MAGNITUDE   0xFFFF
+
+#define DEV_INPUT   "/dev/input"
+#define EVENT          "event"
+
+#define MAX_DATA 16
+#define FF_INFO_MAGIC 0xDEADFEED
+
+#define SET_OVERDRIVE_BIT(x,value) (x |= ((value > 0)? 1: 0)<<14)
+#define SET_LEVEL_BIT(x,value) (x |= (value<<13))
+
+#define BITS_PER_LONG       (sizeof(long) * 8)
+#define LONG(x)         ((x) / BITS_PER_LONG)
+#define OFFSET(x)       ((x) & (BITS_PER_LONG - 1))
+#define test_bit(bit, array)    ((array[LONG(bit)] >> OFFSET(bit)) & 1)
+
+#define safe_free(x) safe_free_memory((void**)&(x))
+
+struct ff_info_header {
+       unsigned int magic;
+       int iteration;
+       int ff_info_data_count;
+};
+
+struct ff_info_data {
+       int type;/* play, stop etc */
+       int magnitude; /* strength */
+       int length; /* in ms for stop, play*/
+};
+
+struct ff_info_buffer {
+       struct ff_info_header header;
+       struct ff_info_data data[MAX_DATA];
+};
+
+struct ff_info {
+       int handle;
+       guint timer;
+       struct ff_effect effect;
+       struct ff_info_buffer *ffinfobuffer;
+       int currentindex;
+};
+
+static int ff_fd;
+static GList *ff_list;
+static GList *handle_list;
+static char ff_path[PATH_MAX];
+static int unique_number;
+static int current_effect_id = -1;
+
+static int stop_device(int device_handle);
+
+static inline void safe_free_memory(void** mem)
+{
+    if (mem && *mem) {
+        free(*mem);
+        *mem = NULL;
+    }
+}
+
+static struct ff_info *read_from_list(int handle)
+{
+       struct ff_info *temp;
+       GList *elem;
+
+       SYS_G_LIST_FOREACH(ff_list, elem, temp) {
+               if (temp->handle == handle)
+                       return temp;
+       }
+       return NULL;
+}
+
+static bool check_valid_handle(struct ff_info *info)
+{
+       struct ff_info *temp;
+       GList *elem;
+
+       SYS_G_LIST_FOREACH(ff_list, elem, temp) {
+               if (temp == info)
+                       break;
+       }
+
+       if (!temp)
+               return false;
+       return true;
+}
+
+static bool check_fd(int *fd)
+{
+       int ffd;
+
+       if (*fd > 0)
+               return true;
+
+       ffd = open(ff_path, O_RDWR);
+       if (ffd < 0)
+               return false;
+
+       *fd = ffd;
+       return true;
+}
+
+static int ff_stop(int fd, struct ff_effect *effect);
+static gboolean timer_cb(void *data)
+{
+       struct ff_info *info = (struct ff_info *)data;
+
+       if (!info) {
+               _E("Failed to check info.");
+               return G_SOURCE_REMOVE;
+       }
+
+       if (!check_valid_handle(info)) {
+               _E("Failed to check valied info.");
+               return G_SOURCE_REMOVE;
+       }
+
+       _I("Stop vibration by timer. id(%d)", info->effect.id);
+
+       /* stop previous vibration */
+       ff_stop(ff_fd, &info->effect);
+
+       /* reset timer */
+       info->timer = 0;
+
+       return G_SOURCE_REMOVE;
+}
+
+static int ff_find_device(void)
+{
+       DIR *dir;
+       struct dirent *dent;
+       char ev_path[PATH_MAX];
+       unsigned long features[1+FF_MAX/sizeof(unsigned long)];
+       int fd, ret;
+
+       dir = opendir(DEV_INPUT);
+       if (!dir)
+               return -errno;
+
+       while (1) {
+               dent = readdir(dir);
+               if (dent == NULL)
+                       break;
+
+               if (dent->d_type == DT_DIR ||
+                       !strstr(dent->d_name, "event"))
+                       continue;
+
+               snprintf(ev_path, sizeof(ev_path), "%s/%s", DEV_INPUT, dent->d_name);
+
+               fd = open(ev_path, O_RDWR);
+               if (fd < 0) {
+                       _E("Failed to open '%s'.: %d", ev_path, errno);
+                       continue;
+               }
+
+               /* get force feedback device */
+               memset(features, 0, sizeof(features));
+               ret = ioctl(fd, EVIOCGBIT(EV_FF, sizeof(features)), features);
+               if (ret == -1) {
+                       close(fd);
+                       continue;
+               }
+
+               if (test_bit(FF_CONSTANT, features))
+                       _D("'%s' type: constant", ev_path);
+               if (test_bit(FF_PERIODIC, features))
+                       _D("'%s' type: periodic", ev_path);
+               if (test_bit(FF_SPRING, features))
+                       _D("'%s' type: spring", ev_path);
+               if (test_bit(FF_FRICTION, features))
+                       _D("'%s' type: friction", ev_path);
+               if (test_bit(FF_RUMBLE, features))
+                       _D("'%s' type: rumble", ev_path);
+
+               if (test_bit(FF_RUMBLE, features)) {
+                       memcpy(ff_path, ev_path, strlen(ev_path)+1);
+                       close(fd);
+                       closedir(dir);
+                       return 0;
+               }
+
+               close(fd);
+       }
+
+       closedir(dir);
+       return -1;
+}
+
+static int ff_init_effect(struct ff_effect *effect)
+{
+       if (!effect)
+               return -EINVAL;
+
+       effect->type = FF_RUMBLE;
+       effect->replay.length = 0;
+       effect->replay.delay = 0;
+       effect->id = -1;
+       effect->u.rumble.strong_magnitude = 0;
+       effect->u.rumble.weak_magnitude = 0;
+
+       return 0;
+}
+
+static int ff_set_effect(struct ff_effect *effect, int duration, int level, int intensity, int frequency, int overdrive)
+{
+       if (!effect) {
+               _E("There is no valid effect.");
+               return -EINVAL;
+       }
+
+       /*
+               __u16 strong_magnitude; // strong_magnitude[15:15] = 0 (Reserved)
+                                      // strong_magnitude[14:14] = Overdrive : 0 (Off) or 1 (On)
+                                      // strong_magnitude[13:0] = Intensity Value : 0 (Stop) or 1 ~ 10000 (Intensity)
+               __u16 weak_magnitude;   // weak_magnitude[15:13] = Intensity Level : 1 ~ 5
+                                      // weak_magnitude[12:0] = Frequency : 0 ~ 8191 (0 ~ 819.1 Hz)
+       */
+
+       effect->u.rumble.strong_magnitude = intensity;
+       SET_OVERDRIVE_BIT(effect->u.rumble.strong_magnitude, overdrive);
+
+       effect->u.rumble.weak_magnitude = frequency;
+       SET_LEVEL_BIT(effect->u.rumble.weak_magnitude, level);
+
+       /* set member variables in effect struct */
+       effect->replay.length = duration;               /* length millisecond */
+
+       //_D("rumble data: strong_magnitude = 0x%x, weak_magnitude = 0x%x", effect->u.rumble.strong_magnitude, effect->u.rumble.weak_magnitude);
+       return 0;
+}
+
+static int ff_play(int fd, struct ff_effect *effect)
+{
+       struct input_event play;
+       int ret;
+
+       if (fd < 0 || !effect) {
+               _E("Fd(%d) or effect(%s) is invalid", fd, effect ? "not null" : "null");
+               return -EINVAL;
+       }
+
+       /* upload an effect */
+       if (current_effect_id == -1) {
+               if (ioctl(fd, EVIOCSFF, effect) == -1) {
+                       _E("Failed to ioctl");
+                       return -errno;
+               }
+               current_effect_id = effect->id;
+       } else
+               effect->id = current_effect_id;
+
+       /* play vibration*/
+       play.type = EV_FF;
+       play.code = effect->id;
+       play.value = 1; /* 1 : PLAY, 0 : STOP */
+
+       ret = write(fd, (const void *)&play, sizeof(play));
+       if (ret == -1) {
+               _E("Failed to write");
+               return -errno;
+       }
+
+       return 0;
+}
+
+static int ff_stop(int fd, struct ff_effect *effect)
+{
+       struct input_event stop;
+       int ret;
+
+       if (fd < 0 || !effect)
+               return -EINVAL;
+
+       if (effect->id == -1) {
+               if (current_effect_id == -1)
+                       return 0;
+               effect->id = current_effect_id;
+       }
+
+       /* Stop vibration */
+       stop.type = EV_FF;
+       stop.code = effect->id;
+       stop.value = 0; /* 1 : PLAY, 0 : STOP */
+       ret = write(fd, (const void *)&stop, sizeof(stop));
+       if (ret == -1)
+               return -errno;
+
+       /* removing an effect from the device */
+       if (ioctl(fd, EVIOCRMFF, effect->id) == -1)
+               return -errno;
+
+       /* reset effect id */
+       effect->id = -1;
+       current_effect_id = -1;
+
+       return 0;
+}
+
+static int get_device_count(int *count)
+{
+       /* suppose there is just one haptic device */
+       if (count)
+               *count = 1;
+
+       return 0;
+}
+
+static int open_device(int *device_handle)
+{
+       struct ff_info *info;
+       int n;
+       bool found = false;
+       GList *elem;
+
+       if (!device_handle)
+               return -EINVAL;
+
+       /* if it is the first element */
+       n = SYS_G_LIST_LENGTH(ff_list);
+       if (n == 0 && !ff_fd) {
+               _I("First element: open ff driver");
+               /* open ff driver */
+               ff_fd = open(ff_path, O_RDWR);
+               if (ff_fd < 0) {
+                       _E("Failed to open %s : %d", ff_path, errno);
+                       return -errno;
+               }
+       }
+
+       /* allocate memory */
+       info = calloc(sizeof(struct ff_info), 1);
+       if (!info) {
+               _E("Failed to allocate memory : %d", errno);
+               return -errno;
+       }
+
+       /* initialize ff_effect structure */
+       ff_init_effect(&info->effect);
+
+       if (unique_number == INT_MAX)
+               unique_number = 0;
+
+       while (found != true) {
+               ++unique_number;
+               elem = SYS_G_LIST_FIND(handle_list, (gpointer)(long)unique_number);
+               if (!elem)
+                       found = true;
+       }
+
+       info->handle = unique_number;
+
+       /* add info to local list */
+       SYS_G_LIST_APPEND(ff_list, info);
+       SYS_G_LIST_APPEND(handle_list, (gpointer)(long)info->handle);
+
+       *device_handle = info->handle;
+       return 0;
+}
+
+static int close_device(int device_handle)
+{
+       struct ff_info *info;
+       int n;
+
+       info = read_from_list(device_handle);
+       if (!info) {
+               _E("Handle %d failed to check info", device_handle);
+               return -ENOENT; /* 2 */
+       }
+
+       if (!check_valid_handle(info)) {
+               _E("Handle %d failed to check valid handle", device_handle);
+               return -EINVAL; /* 22 */
+       }
+
+       if (!check_fd(&ff_fd)) {
+               _E("Handle %d failed to check fd", device_handle);
+               return -ENODEV; /* 19 */
+       }
+
+       /* stop vibration */
+       stop_device(device_handle);
+
+       SYS_G_LIST_REMOVE(handle_list, (gpointer)(long)info->handle);
+
+       safe_free(info->ffinfobuffer);
+       /* remove info from local list */
+       SYS_G_LIST_REMOVE(ff_list, info);
+       safe_free(info);
+
+       /* if it is the last element */
+       n = SYS_G_LIST_LENGTH(ff_list);
+       if (n == 0 && ff_fd) {
+               _I("Last element: close ff driver");
+               /* close ff driver */
+               close(ff_fd);
+               ff_fd = 0;
+       }
+
+       return 0;
+}
+
+static int vibrate_monotone(int device_handle, int duration, int frequency, int overdrive, int level, int intensity, int priority)
+{
+       struct ff_info *info;
+       int ret;
+
+       info = read_from_list(device_handle);
+       if (!info) {
+               _E("Handle %d failed to check list.", device_handle);
+               return -EINVAL;
+       }
+
+       if (!check_valid_handle(info)) {
+               _E("Handle %d failed to check handle.", device_handle);
+               return -EINVAL;
+       }
+
+       if (!check_fd(&ff_fd))
+               return -ENODEV;
+
+       if (duration <= 0) {
+               _I("Handle %d skip requests with duration 0.", device_handle);
+               return 0;
+       }
+
+       /* Zero(0) is the infinitely vibration value */
+       if (duration == HAPTIC_MODULE_DURATION_UNLIMITED)
+               duration = 0;
+
+       /* unregister existing timer */
+       if (info->timer) {
+               ff_stop(ff_fd, &info->effect);
+               g_source_remove(info->timer);
+               info->timer = 0;
+       }
+
+       /* set effect as per arguments */
+       ff_init_effect(&info->effect);
+       ret = ff_set_effect(&info->effect, duration, level, intensity, frequency, overdrive);
+       if (ret < 0) {
+               _E("Handle %d fail to set effect(duration:%d, level:%d, intensity:%d, frequency:%d, overdrive:%d) : %d",
+                               device_handle, duration, level, intensity, frequency, overdrive, ret);
+               return ret;
+       }
+
+       /* play effect as per arguments */
+       ret = ff_play(ff_fd, &info->effect);
+       if (ret < 0) {
+               _E("Handle %d fail to play haptic effect(fd:%d id:%d) : %d",
+                               device_handle, ff_fd, info->effect.id, ret);
+               return ret;
+       }
+
+       _I("Play vibration. Handle %d effect id : %d %dms", device_handle, info->effect.id, duration);
+
+       /* register timer */
+       if (duration) {
+               info->timer = g_timeout_add(duration, timer_cb, info);
+               if (!info->timer)
+                       _E("Handle %d failed to add timer callback", device_handle);
+       }
+
+       return 0;
+}
+
+static int stop_device(int device_handle)
+{
+       struct ff_info *info;
+       int r;
+
+       info = read_from_list(device_handle);
+       if (!info) {
+               _E("Handle %d fail to check info", device_handle);
+               return -ENOENT; /* 2 */
+       }
+
+       if (!check_fd(&ff_fd)) {
+               _E("Handle %d fail to check fd", device_handle);
+               return -ENODEV; /* 19 */
+       }
+
+       /* stop effect */
+       r = ff_stop(ff_fd, &info->effect);
+       if (r < 0)
+               _E("failed to stop effect(id:%d) : %d", info->effect.id, r);
+       else
+               _I("Stop vibration by request. id(%d)", info->effect.id);
+
+       /* unregister existing timer */
+       if (r >= 0 && info->timer) {
+               g_source_remove(info->timer);
+               info->timer = 0;
+       }
+
+       return 0;
+}
+
+static bool is_valid(void)
+{
+       int ret;
+
+       ret = ff_find_device();
+       if (ret < 0) {
+               _E("Do not support standard haptic device");
+               return false;
+       }
+
+       _I("Support standard haptic device");
+       return true;
+}
+
+static int haptic_init(void **data)
+{
+       hal_backend_haptic_funcs *haptic_funcs;
+
+       haptic_funcs = calloc(1, sizeof(hal_backend_haptic_funcs));
+       if (!haptic_funcs)
+               return -ENOMEM;
+
+       haptic_funcs->get_device_count = get_device_count;
+       haptic_funcs->open_device = open_device;
+       haptic_funcs->close_device = close_device;
+       haptic_funcs->vibrate_monotone = vibrate_monotone;
+       haptic_funcs->stop_device = stop_device;
+       haptic_funcs->is_valid = is_valid;
+
+       *data = (void *)haptic_funcs;
+
+       return 0;
+}
+
+static int haptic_exit(void *data)
+{
+       if (!data)
+               return -EINVAL;
+
+       free(data);
+       return 0;
+}
+
+hal_backend EXPORT hal_backend_device_haptic_data = {
+       .name = "haptic",
+       .vendor = "R800",
+       .abi_version = HAL_ABI_VERSION_TIZEN_6_5,
+       .init = haptic_init,
+       .exit = haptic_exit,
+};
index 5cc7a17..9c1096a 100644 (file)
@@ -13,6 +13,9 @@ BuildRequires:    pkgconfig(glib-2.0)
 BuildRequires:    pkgconfig(libusbgx)
 BuildRequires:    pkgconfig(libudev)
 BuildRequires:    pkgconfig(capi-system-info)
+BuildRequires:    pkgconfig(hal-api-common)
+BuildRequires:    pkgconfig(hal-api-device)
+BuildRequires:    pkgconfig(libsyscommon)
 Requires(post):   /sbin/ldconfig
 Requires(postun): /sbin/ldconfig
 
@@ -41,3 +44,4 @@ make %{?jobs:-j%jobs}
 %manifest %{name}.manifest
 %license LICENSE.Apache-2.0
 %{_libdir}/hw/*.so
+/hal/lib/*.so*