--- /dev/null
+/*
+ * 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-standard",
+ .vendor = "",
+ .abi_version = HAL_ABI_VERSION_TIZEN_6_5,
+ .init = haptic_init,
+ .exit = haptic_exit,
+};