From ef999bf6ae43ef567c76ac0b473c7d4340760ab2 Mon Sep 17 00:00:00 2001 From: Per Persson Date: Fri, 20 May 2011 11:40:47 +0200 Subject: HDMIservice added Initial commit ST-Ericsson ID: 326691 ST-Ericsson FOSS-OUT ID: STETL-FOSS-OUT-10177 Signed-off-by: Per Persson --- Android.mk | 36 ++ include/hdmi_service_api.h | 106 ++++ include/hdmi_service_local.h | 358 ++++++++++++++ src/cec.c | 149 ++++++ src/edid.c | 538 ++++++++++++++++++++ src/hdcp.c | 257 ++++++++++ src/hdmi_service.c | 1104 ++++++++++++++++++++++++++++++++++++++++++ src/hdmi_service_api.c | 90 ++++ src/hdmi_service_start.c | 29 ++ src/kevent.c | 159 ++++++ src/setres.c | 400 +++++++++++++++ src/socket.c | 316 ++++++++++++ 12 files changed, 3542 insertions(+) create mode 100644 Android.mk create mode 100644 include/hdmi_service_api.h create mode 100644 include/hdmi_service_local.h create mode 100644 src/cec.c create mode 100644 src/edid.c create mode 100644 src/hdcp.c create mode 100644 src/hdmi_service.c create mode 100644 src/hdmi_service_api.c create mode 100644 src/hdmi_service_start.c create mode 100644 src/kevent.c create mode 100644 src/setres.c create mode 100644 src/socket.c diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..4d557f2 --- /dev/null +++ b/Android.mk @@ -0,0 +1,36 @@ +# Copyright (C) 2008 The Android Open Source Project +# +# 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. + +# Library +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_PRELINK_MODULE := false +LOCAL_SRC_FILES := src/hdmi_service_api.c src/hdmi_service.c src/cec.c \ + src/edid.c src/hdcp.c src/setres.c src/kevent.c src/socket.c +LOCAL_CFLAGS := -DANDROID +LOCAL_C_INCLUDES += $(LOCAL_PATH)/include +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_MODULE := libhdmi_service +LOCAL_MODULE_TAGS := eng +include $(BUILD_SHARED_LIBRARY) + +# Executable, to be used to start service when there is no daemon +include $(CLEAR_VARS) +LOCAL_PRELINK_MODULE := false +LOCAL_SRC_FILES := src/hdmi_service_start.c +LOCAL_C_INCLUDES += $(LOCAL_PATH)/include +LOCAL_SHARED_LIBRARIES := libhdmi_service +LOCAL_MODULE := hdmi_service_st +LOCAL_MODULE_TAGS := eng +include $(BUILD_EXECUTABLE) diff --git a/include/hdmi_service_api.h b/include/hdmi_service_api.h new file mode 100644 index 0000000..8133d33 --- /dev/null +++ b/include/hdmi_service_api.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _HDMI_SERVICE_API_H +#define _HDMI_SERVICE_API_H + +/* + * Set to 1 to stay alive when system suspends. + * Set to 0 to sleep when system suspends. + */ +#define HDMI_SERVICE_STAY_ALIVE_DURING_SUSPEND 1 + +/* If defined, socket usage is hidden for messages from service, + * and a callback function is used instead + */ +/*#define HDMI_SERVICE_USE_CALLBACK_FN*/ + +/* Service initialisation, threads creation + * Input avoid_return_msg: set to 1 to avoid messages from service. + * Return value: socket number where events will be notified. + */ +int hdmi_init(int avoid_return_msg); + +/* Service exit, threads destruction */ +int hdmi_exit(void); + +/* Enable HDMI HW */ +int hdmi_enable(void); + +/* Disable HDMI HW */ +int hdmi_disable(void); + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +/* Set callback for reception of messages from service */ +void hdmi_callback_set(void (*hdmi_cb)(int cmd, int data_size, __u8 *data)); +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + +/* Change resolution. cea=0: VESA resolution, cea=1: CEA resolution */ +int hdmi_resolution_set(int cea, int vesaceanr); + +/* Release frame buffer */ +int hdmi_fb_release(void); + +/* Send CEC message */ +int hdmi_cec_send(__u8 initiator, __u8 destination, __u8 data_size, __u8 *data); + +/* Manually request EDID */ +int hdmi_edid_request(__u8 block); + +/* Initialise HDCP. AES data is required */ +int hdmi_hdcp_init(__u16 aes_size, __u8 *aes_data); + +/* Send Infoframe */ +int hdmi_infoframe_send(__u8 type, __u8 version, __u8 crc, __u8 data_size, + __u8 *data); + +/* Set preferred resolution priorities */ +int hdmi_vesa_cea_prio_set(__u8 vesa_cea1, __u8 nr1, + __u8 vesa_cea2, __u8 nr2, + __u8 vesa_cea3, __u8 nr3); + + +/* Messages from service */ + +/* cmd=HDMI_EDIDRESP data format + *u8 result (0 = ok, 1 = not ok) + *u8 edid_data[128] (if result == ok) + */ + +/* cmd=HDMI_HDCPSTATE data format + *u8 state + * state = 0: No Receiver state + * state = 1: Receiver connected state + * state = 2: No HDCP receiver state + * state = 3: No Encryption state + * state = 4: Authentication on going state + * state = 5: Authentication fail state + * state = 6: Authentication succeed state + * state = 7: Encryption on going state + */ + +/* HDMI message cmd sent from hdmi_service */ +#define HDMI_PLUGGED_EV 0x10 +#define HDMI_UNPLUGGED_EV 0x11 +#define HDMI_EDIDRESP 0x12 +#define HDMI_CECRECVD 0x13 +#define HDMI_ILLSTATE_POWERED 0x80 +#define HDMI_ILLSTATE_UNPOWERED 0x81 +#define HDMI_ILLSTATE_UNPLUGGED 0x82 +#define HDMI_ILLSTATE_PWRON_PLUGGED 0x83 +#define HDMI_CECSENDERR 0x84 +#define HDMI_HDCPSTATE 0x85 + +#endif /* #ifdef _HDMI_SERVICE_API_H */ + +#ifdef __cplusplus +} +#endif diff --git a/include/hdmi_service_local.h b/include/hdmi_service_local.h new file mode 100644 index 0000000..83f2bcb --- /dev/null +++ b/include/hdmi_service_local.h @@ -0,0 +1,358 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _HDMI_SERVICE_LOCAL_H +#define _HDMI_SERVICE_LOCAL_H + +enum hdmi_fb_state { + HDMI_FB_CLOSED, + HDMI_FB_OPENED +}; + +enum hdmi_plug_state { + HDMI_UNPLUGGED, + HDMI_PLUGGED, + HDMI_PLUGUNDEF +}; + +enum hdmi_power_state { + HDMI_POWEROFF, + HDMI_POWERON, + HDMI_POWERUNDEF +}; + +enum hdmi_format { + HDMI_FORMAT_HDMI, + HDMI_FORMAT_SDTV, + HDMI_FORMAT_DVI +}; + +struct cmd_data { + __u32 cmd; + __u32 cmd_id; + __u32 data_len; + __u8 data[512]; + struct cmd_data *next; +}; + +struct video_format { + __u8 cea; /* 0=VESA, 1=CEA */ + __u8 vesaceanr; + __u8 sink_support; + __u8 prio; +}; + +struct vesacea { + __u8 cea; + __u8 nr; +}; + +struct edid_latency { + int video_latency; + int audio_latency; + int intlcd_video_latency; + int intlcd_audio_latency; +}; + +typedef void(*cb_fn)(int cmd, int data_length, __u8 *data); + +int cecrx_subscribe(void); +int cecsend(__u32 cmd_id, __u8 in, __u8 dest, __u8 len, __u8 *data); +int cecrx(void); +int edid_read(__u8 block, __u8 *data); +int edid_parse0(__u8 *data, __u8 *extension, struct video_format *, int size); +int edid_parse1(__u8 *data, struct video_format formats[], int nr_formats, + int *basic_audio_support, struct edid_latency *edid_latency); +int edidreq(__u8 block, __u32 cmd_id); +int hdcp_init(__u8 *aes); +int hdcp_state(void); +int video_formats_clear(void); +int vesacea_supported(int *nr_supported, struct vesacea vesacea[]); +int video_formats_supported_hw(void); +int nr_formats_get(void); +struct video_format *video_formats_get(void); +void set_vesacea_prio_all(void); +int hdmi_fb_chres(__u8 cea, __u8 vesaceanr); +int vesaceaprio_set(__u8 len, __u8 *data); +int hdmievclr(__u8 mask); +void thread_kevent_fn(void *arg); +int hdmiplug_subscribe(void); +int hdmi_event(int event); +int get_best_videoformat(__u8 *cea, __u8 *vesaceanr); +int listensocket_set(int sock); +int listensocket_get(void); +int clientsocket_get(void); +int cecsenderr(void); +int get_new_cmd_id_ind(void); +void thread_socklisten_fn(void *arg); +int cmd_add(struct cmd_data *cmd); +int serversocket_create(int avoid_return_msg); +int serversocket_write(int len, __u8 *data); +int serversocket_close(void); +int poweronoff(__u8 onoff); +int clientsocket_send(__u8 *buf, int len); + +int hdmi_service_init(int avoid_return_msg); +int hdmi_service_exit(void); +int hdmi_service_enable(void); +int hdmi_service_disable(void); + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +void hdmi_service_callback_set(cb_fn hdmi_cb); +cb_fn hdmi_service_callback_get(void); +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + +int hdmi_service_resolution_set(int cea, int vesaceanr); +int hdmi_service_fb_release(void); +int hdmi_service_cec_send(__u8 initiator, __u8 destination, __u8 data_size, + __u8 *data); +int hdmi_service_edid_request(__u8 block); +int hdmi_service_hdcp_init(__u16 aes_size, __u8 *aes_data); +int hdmi_service_infoframe_send(__u8 type, __u8 version, __u8 crc, + __u8 data_size, __u8 *data); +int hdmi_service_vesa_cea_prio_set(__u8 vesa_cea1, __u8 nr1, + __u8 vesa_cea2, __u8 nr2, + __u8 vesa_cea3, __u8 nr3); + +#define AES_KEYS_SIZE 297 +#define FORMATS_MAX 35 + +#define false 0 +#define true 1 +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define LOGHDMILIB LOGE +#define LOGHDMILIB2 LOGE +#define LOGHDMILIB3(format, ...) + +#ifdef ANDROID +#define FBPATH "/dev/graphics/" +#else +#define FBPATH "/dev/" +#endif + +#define SOCKET_LISTEN_PATH "/dev/socket/hdmi_listen" + +#define STOREASTEXT_FILE "/sys/class/misc/hdmi/storeastext" +#define PLUGDETEN_FILE "/sys/class/misc/hdmi/plugdeten" +#define EVENT_FILE "/sys/class/misc/hdmi/evread" +#define EVENTCLR_FILE "/sys/class/misc/hdmi/evclr" +#define EVWAKEUP_FILE "/sys/class/misc/hdmi/evwakeup" +#define EDIDREAD_FILE "/sys/class/misc/hdmi/edidread" +#define DISPONOFF_FILE "/sys/devices/av8100_hdmi.2/disponoff" +#define HDCPCHKAESOTP_FILE "/sys/class/misc/hdmi/hdcpchkaesotp" +#define HDCPLOADAES_FILE "/sys/class/misc/hdmi/hdcploadaes" +#define HDCPSTATEGET_FILE "/sys/class/misc/hdmi/hdcpstateget" +#define HDCPAUTH_FILE "/sys/class/misc/hdmi/hdcpauthencr" +#define HDCPEVEN_FILE "/sys/class/misc/hdmi/hdcpeven" +#define CECSEND_FILE "/sys/class/misc/hdmi/cecsend" +#define CECRXEVEN_FILE "/sys/class/misc/hdmi/ceceven" +#define CECREAD_FILE "/sys/class/misc/hdmi/cecread" +#define INFOFRSEND_FILE "/sys/class/misc/hdmi/infofrsend" +#define POWERONOFF_FILE "/sys/class/misc/hdmi/poweronoff" +#define HDMIFORMAT_FILE "/sys/devices/av8100_hdmi.2/hdmisdtvswitch" +#define VESACEAFORMATS_FILE "/sys/devices/av8100_hdmi.2/vesacea" +#define TIMING_FILE "/sys/devices/av8100_hdmi.2/timing" +#define STAYALIVE_FILE "/sys/devices/av8100_hdmi.2/stayalive" + +#define STOREASTEXT_STR "01" /* Use hextext format in sysfs files */ +#define STOREASBIN_STR "00" /* Use binary format in sysfs files */ + +#define HDMI_PLUGGED_IN_EVSTR "01" +#define HDMI_PLUGGED_OUT_EVSTR "02" +#define HDMI_CEC_EVSTR "04" +#define HDMI_HDCP_EVSTR "08" +#define HDMI_CECTXERR_EVSTR "10" +#define HDMI_USER_EVSTR "20" + +#define EDIDREAD_SIZE 0x80 +#define POLL_READ_SIZE 1 +#define CEAPRIO_MAX_SIZE 10 +#define VESACEAPRIO_DEFAULT 254 +#define OTP_UNPROGGED 0 +#define OTP_PROGGED 1 +#define TIMING_SIZE 32 +#define CEC_MSG_SIZE_MAX 15 +#define INFOFR_MSG_SIZE_MAX 27 + +#define HDMIEVENT_POLLSIZEFAIL -1 +#define HDMIEVENT_EVENTUNKNOWN -2 +#define HDMIEVENT_NOEVENT 0 +#define HDMIEVENT_HDMIPLUGGED 0x1 +#define HDMIEVENT_HDMIUNPLUGGED 0x2 +#define HDMIEVENT_CEC 0x4 +#define HDMIEVENT_HDCP 0x8 +#define HDMIEVENT_CECTXERR 0x10 +#define HDMIEVENT_WAKEUP 0x20 + +#define EVENTMASK_ALL 0xFF +#define EVENTMASK_PLUG 0x03 + +/* User commands */ +#define HDMIEVENT_CMD 0x010000 +#define CMD_OFFSET 0 +#define CMDID_OFFSET 4 +#define CMDLEN_OFFSET 8 +#define CMDBUF_OFFSET 12 + + +#define LOADAES_OK 0 +#define LOADAES_NOT_OK -1 +#define LOADAES_NOT_FUSED -2 +#define LOADAES_CRC_MISMATCH -3 + +#define RESULT_OK 0 +#define STOREAS_FAIL -1 +#define SYSFS_FILE_FAILED -2 +#define EDIDREAD_FAIL -3 +#define EDIDREAD_NOEXT -4 +#define EDIDREAD_NOVIDEO -5 +#define HDCP_OK 0 +#define AESKEYS_FAIL -1 +#define HDCPSTATE_FAIL -2 +#define HDCPAUTHENCR_FAIL -3 + +#define EDID_BL0_HEADER_OFFSET 0x00 +#define EDID_BL0_VERSION_OFFSET 0x12 +#define EDID_BL0_REVISION_OFFSET 0x13 +#define EDID_BL0_EST_TIMING1_OFFSET 0x23 +#define EDID_BL0_EST_TIMING2_OFFSET 0x24 +#define EDID_BL0_EXTFLAG_OFFSET 0x7E +#define EDID_BL0_ESTTIM1_OFFSET 0x23 +#define EDID_BL0_ESTTIM2_OFFSET 0x24 +#define EDID_BL0_STDTIM1_OFFSET 0x26 +#define EDID_BL1_REVNR_OFFSET 0x01 +#define EDID_BL1_OFFSET_OFFSET 0x02 +#define EDID_BL1_AUDIO_SUPPORT_OFFSET 0x03 +#define EDID_BL1_ESTTIM3_1_FLAG_OFFSET 0x48 +#define EDID_BL1_ESTTIM3_2_FLAG_OFFSET 0x5A +#define EDID_BL1_ESTTIM3_3_FLAG_OFFSET 0x6C +#define EDID_BL1_ESTTIM3_BYTE_START 6 +#define EDID_BL1_ESTTIM3_BYTE_END 11 +#define EDID_BL1_STDTIM9_1_FLAG_OFFSET 0x48 +#define EDID_BL1_STDTIM9_2_FLAG_OFFSET 0x5A +#define EDID_BL1_STDTIM9_3_FLAG_OFFSET 0x6C +#define EDID_BL1_STDTIM9_BYTE_START 5 +#define EDID_SVD_ID_MASK 0x7F +#define EDID_EXTVER_3 0x03 +#define EDID_NO_DATA 0x04 +#define EDID_BLK_START 0x04 +#define EDID_BLK_CODE_MSK 0xE0 +#define EDID_BLK_CODE_SHIFT 5 +#define EDID_BLK_LENGTH_MSK 0x1F +#define EDID_CODE_VIDEO 0x02 +#define EDID_CODE_VSDB 0x03 +#define EDID_BL0_STDTIM1_SIZE 8 +#define EDID_BL1_STDTIM9_SIZE 6 +#define EDID_STDTIM_AR_MASK 0xC0 +#define EDID_STDTIM_AR_SHIFT 6 +#define EDID_STDTIM_FREQ_MASK 0x3F +#define EDID_STDTIM_FREQ_SHIFT 0 +#define EDID_BASIC_AUDIO_SUPPORT_MASK 0x40 +#define EDID_VSD_PHYS_SRC 4 +#define EDID_VSD_LATENCY_IND 8 +#define EDID_VSD_LAT_FLD_MASK 0x80 +#define EDID_VSD_INTLCD_LAT_FLD_MASK 0x40 +#define EDID_VSD_VID_LAT 9 +#define EDID_VSD_AUD_LAT 10 +#define EDID_VSD_INTLCD_VID_LAT 11 +#define EDID_VSD_INTLCD_AUD_LAT 12 + +/* HDCP states */ +#define HDCP_STATE_NO_RECV 0 +#define HDCP_STATE_RECV_CONN 1 +#define HDCP_STATE_NO_HDCP 2 +#define HDCP_STATE_NO_ENCR 3 +#define HDCP_STATE_AUTH_ONGOING 4 +#define HDCP_STATE_AUTH_FAIL 5 +#define HDCP_STATE_AUTH_SUCCEDED 6 +#define HDCP_STATE_ENCR_ONGOING 7 + +#define VIDEO_FORMAT_DEFAULT 1 /* 640x480@60P */ + +#define STARTUP_DELAY_US 6000000 +#define HDCPAUTH_WAITTIME 1000000 +#define LOADAES_WAITTIME 250000 +#define EDIDREAD_WAITTIME 2000000 + +/* Socket listen thread */ +#define SOCKET_DATA_MAX 256 +#define SOCKET_MAX_CONN 1 + +/* Command format */ +/* Message data format + *u32 cmd + *u32 cmd_id + *u32 size + *u8 data[size] data format is specified below + */ + +/* HDMI message cmd sent to hdmi_service */ +#define HDMI_ENABLE 0x1 + +#define HDMI_DISABLE 0x2 + +/* cmd=HDMI_EDIDREQ data format + *u8 block (0 or 1) + */ +#define HDMI_EDIDREQ 0x3 + +/* cmd=HDMI_CECSEND and HDMI_CECRECVD data format + *u8 initiator + *u8 destination + *u8 cec_data_size + *u8 cec_data[cec_data_size] + */ +#define HDMI_CECSEND 0x4 + +/* cmd=HDMI_FB_RES_SET data format + *u8 vesa(0)/cea(1) + *u8 vesa/cea nr + */ +#define HDMI_FB_RES_SET 0x5 + +#define HDMI_FB_RELEASE 0x6 + +/* cmd=HDMI_HDCP_INIT data format + *u8 aesdata[297] + */ +#define HDMI_HDCP_INIT 0x7 + +/* cmd=HDMI_VESACEAPRIO_SET data format + *u8 nrofprios + *u8 vesa/cea[0] 0=VESA, 1=CEA + *u8 vesaceanr[0] vesanr or ceanr Prio 1 + *u8 vesa/cea[1] 0=VESA, 1=CEA + *u8 vesaceanr[1] vesanr or ceanr Prio 2 + *.... + *u8 vesa/cea[size-1] 0=VESA, 1=CEA + *u8 vesaceanr[size-1] vesanr or ceanr Prio size + */ +#define HDMI_VESACEAPRIO_SET 0x8 + +/* cmd=HDMI_INFOFR data format + *u8 type + *u8 version + *u8 crc + *u8 size + *u8 data[size] + */ +#define HDMI_INFOFR 0x9 + +#define HDMI_EXIT 0xFF + + + +#endif /* #ifdef _HDMI_SERVICE_LOCAL_H */ + +#ifdef __cplusplus +} +#endif diff --git a/src/cec.c b/src/cec.c new file mode 100644 index 0000000..d5125d9 --- /dev/null +++ b/src/cec.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* String handling */ +#include +#include +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +const __u8 cecrxeven_val[] = {0x01}; /* Enable CEC RX events */ +int cectx_cmd_id; + +static int cectxcmdid_set(int cmd_id) +{ + cectx_cmd_id = cmd_id; + return 0; +} + +static int cectxcmdid_get(void) +{ + return cectx_cmd_id; +} + +/* Subscribe for incoming CEC messages */ +int cecrx_subscribe(void) +{ + int cecrxfd; + + cecrxfd = open(CECRXEVEN_FILE, O_WRONLY); + if (cecrxfd < 0) { + LOGHDMILIB(" failed to open %s", CECRXEVEN_FILE); + return -1; + } + write(cecrxfd, cecrxeven_val, sizeof(cecrxeven_val)); + close(cecrxfd); + return 0; +} + +int cecsenderr(void) +{ + int val; + __u8 buf[32]; + int cmd_id; + + cmd_id = cectxcmdid_get(); + + val = HDMI_CECSENDERR; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = 0; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + + /* Send on socket */ + return clientsocket_send(buf, CMDBUF_OFFSET + val); +} + +/* Send CEC message */ +int cecsend(__u32 cmd_id, __u8 in, __u8 dest, __u8 len, __u8 *data) +{ + int cecsendfd; + int res; + char buf[128]; + + LOGHDMILIB("%s begin", __func__); + + cectxcmdid_set(cmd_id); + + res = 0; + buf[0] = in; + buf[1] = dest; + buf[2] = len; + memcpy(&buf[3], data, len); + + /* Send CEC cmd */ + cecsendfd = open(CECSEND_FILE, O_WRONLY); + if (cecsendfd <= 0) { + LOGHDMILIB("***** Failed to open %s *****\n", CECSEND_FILE); + goto cecsend_err; + } + + res = write(cecsendfd, buf, len + 3); + if (res != len + 3) { + LOGHDMILIB("***** cecsend failed %d *****\n", res); + close(cecsendfd); + goto cecsend_err; + } + + close(cecsendfd); + + LOGHDMILIB("%s end", __func__); + return 0; + +cecsend_err: + cecsenderr(); + LOGHDMILIB("%s end", __func__); + return -1; +} + +/* Read received CEC message and forward on client socket */ +int cecrx(void) +{ + int cecreadfd; + __u8 buf[32]; + __u8 cecdata[32]; + int cecsize; + int cnt; + int val; + int res = 0; + __u32 cmd_id; + + LOGHDMILIB("%s begin", __func__); + + cmd_id = get_new_cmd_id_ind(); + cecreadfd = open(CECREAD_FILE, O_RDONLY); + if (cecreadfd < 0) { + LOGHDMILIB("***** Failed to open %s *****", CECREAD_FILE); + return -1; + } + cecsize = read(cecreadfd, buf, sizeof(buf)); + close(cecreadfd); + + for (cnt = 0; cnt < cecsize; cnt++) + LOGHDMILIB2("cecrx[%d]:%x", cnt, buf[cnt]); + + val = HDMI_CECRECVD; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = cecsize; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + memcpy(&buf[CMDBUF_OFFSET], cecdata, val); + + /* Send on socket */ + res = clientsocket_send(buf, CMDBUF_OFFSET + val); + + LOGHDMILIB("%s end", __func__); + return res; +} diff --git a/src/edid.c b/src/edid.c new file mode 100644 index 0000000..1733a73 --- /dev/null +++ b/src/edid.c @@ -0,0 +1,538 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* String handling */ +#include +#include +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +struct edid_stdtim_ar { + int x; + int y; +}; + +const __u8 edidreqbl0[] = {0xA0, 0x00}; /* Request EDID block 0 */ +const __u8 edidreqbl1[] = {0xA0, 0x01}; /* Request EDID block 1 */ +const __u8 edid_block0_start[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; +const __u8 edid_stdtim9_flag[] = {0x00, 0x00, 0x00, 0xFA, 0x00}; +const __u8 edid_esttim3_flag[] = {0x00, 0x00, 0x00, 0xF7, 0x00}; +const __u8 edid_esttim1_2_offset[] = {EDID_BL0_ESTTIM1_OFFSET, + EDID_BL0_ESTTIM2_OFFSET}; +const __u8 edid_esttim3_flag_offset[] = {EDID_BL1_ESTTIM3_1_FLAG_OFFSET, + EDID_BL1_ESTTIM3_2_FLAG_OFFSET, + EDID_BL1_ESTTIM3_3_FLAG_OFFSET}; +const __u8 edid_stdtim9_flag_offset[] = {EDID_BL1_STDTIM9_1_FLAG_OFFSET, + EDID_BL1_STDTIM9_2_FLAG_OFFSET, + EDID_BL1_STDTIM9_3_FLAG_OFFSET}; +/* Aspect ratios */ +const struct edid_stdtim_ar edid_stdtim_ar[] = { + {16, 10}, + {4, 3}, + {5, 4}, + {16, 9} +}; + +struct vesa_modes { + int xres; + int yres; + int freq; + int vesa_nr; +}; + +static struct vesa_modes vesa_modes[] = { + {800, 600, 60, 9}, + {848, 480, 60, 14}, + {1024, 768, 60, 16}, + {1280, 768, 60, 23}, + {1280, 800, 60, 28}, + {1360, 768, 60, 39}, + {1366, 768, 60, 81} +}; + +static int get_vesanr_from_est_timing(int timing, int byte, int bit) +{ + int vesa_nr = -1; + + LOGHDMILIB2("timing:%d bit:%d", timing, bit); + + switch (timing) { + /* Established Timing 1 */ + case 1: + switch (bit) { + case 5: + vesa_nr = 4; + break; + case 0: + vesa_nr = 9; + break; + default: + break; + } + break; + /* Established Timing 2 */ + case 2: + switch (bit) { + case 3: + vesa_nr = 16; + break; + default: + break; + } + break; + /* Established Timing 3 */ + case 3: + switch (byte) { + case 6: + switch (bit) { + case 3: + vesa_nr = 14; + break; + default: + break; + } + break; + case 7: + switch (bit) { + case 7: + vesa_nr = 23; + break; + case 6: + vesa_nr = 22; + break; + default: + break; + } + break; + case 8: + switch (bit) { + case 7: + vesa_nr = 39; + break; + default: + break; + } + break; + } + break; + default: + break; + } + + return vesa_nr; +} + +int get_vesanr_from_std_timing(int xres, int yres, int freq) +{ + int vesa_nr = -1; + int nr_of_timings; + int index; + + nr_of_timings = sizeof(vesa_modes)/sizeof(vesa_modes[0]); + for (index = 0; index < nr_of_timings; index++) { + if ((xres == vesa_modes[index].xres) && + (yres == vesa_modes[index].yres) && + (freq == vesa_modes[index].freq)) { + vesa_nr = vesa_modes[index].vesa_nr; + break; + } + } + + return vesa_nr; +} + +/* Request and read EDID message for specified block */ +int edid_read(__u8 block, __u8 *data) +{ + int edidread; + int res; + int result = 0; + __u8 buf[16]; + int size; + + LOGHDMILIB("EDID read blk %d", block); + + /* Request edid block 0 */ + edidread = open(EDIDREAD_FILE, O_RDWR); + if (edidread < 0) { + LOGHDMILIB("***** Failed to open %s *****", EDIDREAD_FILE); + result = -1; + goto edid_read_end2; + } + + if (block == 0) { + size = sizeof(edidreqbl0); + memcpy(buf, edidreqbl0, size); + } else { + size = sizeof(edidreqbl1); + memcpy(buf, edidreqbl1, size); + } + + res = write(edidread, buf, size); + if (res < 0) { + LOGHDMILIB("***** Failed to write %s *****", EDIDREAD_FILE); + result = -2; + goto edid_read_end1; + } + + /* Check edid response */ + lseek(edidread, 0, SEEK_SET); + res = read(edidread, data, EDIDREAD_SIZE); + if (res != EDIDREAD_SIZE) { + LOGHDMILIB("***** %s read error size: %d *****", EDIDREAD_FILE, + res); + result = -3; + goto edid_read_end1; + } + +edid_read_end1: + close(edidread); +edid_read_end2: + return result; +} + +/* Parse EDID block 0 */ +int edid_parse0(__u8 *data, __u8 *extension, struct video_format formats[], + int nr_formats) +{ + __u8 version; + __u8 revision; + __u8 est_timing; + int findex; + int vesa_nr; + int bit; + int cnt; + int index; + int xres; + int yres; + int byte; + int ar_index; + int freq; + __u8 edidp; + + *extension = 0; + + /* Header */ + if (memcmp(data + EDID_BL0_HEADER_OFFSET, edid_block0_start, 8) != 0) { + LOGHDMILIB("edid response:\n%02x %02x %02x %02x %02x %02x %02x " + "%02x", + *(data + 1), + *(data + 2), + *(data + 3), + *(data + 4), + *(data + 5), + *(data + 6), + *(data + 7), + *(data + 8) + ); + return EDIDREAD_FAIL; + } else { + LOGHDMILIB("%s", "--- EDID block 0 start OK ---"); + } + + /* Ver and Rev */ + version = *(data + EDID_BL0_VERSION_OFFSET); + revision = *(data + EDID_BL0_REVISION_OFFSET); + LOGHDMILIB("Ver:%d Rev:%d", version, revision); + + /* Read Established Timings 1&2 and set sink_support */ + for (index = 0; index <= 1; index++) { + est_timing = *(data + edid_esttim1_2_offset[index]); + LOGHDMILIB2("EstTim%d:%02x", index + 1, est_timing); + if (est_timing == 0) + continue; + + for (bit = 7; bit >= 0; bit--) { + if (est_timing & (1 << bit)) { + vesa_nr = get_vesanr_from_est_timing(index + 1, + 0, bit); + LOGHDMILIB2("vesa_nr:%d", vesa_nr); + if (vesa_nr < 1) + continue; + + LOGHDMILIB2("EstTim1&2 try vesa_nr:%d", + vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("EstTim1&2 %d " + "vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + } + } + + /* Read Standard Timings 1-8 and set sink_support*/ + for (index = 0; index < EDID_BL0_STDTIM1_SIZE; index++) { + edidp = EDID_BL0_STDTIM1_OFFSET + index * 2; + xres = (*(data + edidp) + 31) * 8; + byte = *(data + edidp + 1); + ar_index = (byte * EDID_STDTIM_AR_MASK) >> EDID_STDTIM_AR_SHIFT; + yres = xres * edid_stdtim_ar[ar_index].y / + edid_stdtim_ar[ar_index].x; + freq = (byte * EDID_STDTIM_FREQ_MASK) >> EDID_STDTIM_FREQ_SHIFT; + vesa_nr = get_vesanr_from_std_timing(xres, yres, freq); + if (vesa_nr < 1) + continue; + + LOGHDMILIB2("StdTim1to8 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("StdTim1to8 %d vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + + if (*(data + EDID_BL0_EXTFLAG_OFFSET) != 0) + *extension = 1; + + return RESULT_OK; +} + +/* Parse EDID block 1 */ +int edid_parse1(__u8 *data, struct video_format formats[], int nr_formats, + int *basic_audio_support, struct edid_latency *edid_latency) +{ + __u8 rev; + __u8 offset; + __u8 blockp; + __u8 code; + __u8 length = 0; + __u8 ceanr; + int index; + int index2; + int cnt; + __u8 est_timing3; + int byte; + int bit; + int vesa_nr; + int xres; + int yres; + int ar_index; + int freq; + __u8 edidp; + __u8 *p; + + rev = *(data + EDID_BL1_REVNR_OFFSET); + offset = *(data + EDID_BL1_OFFSET_OFFSET); + + LOGHDMILIB("rev:%d offset:%d", rev, offset); + + if ((rev != EDID_EXTVER_3) || (offset == 0) || + (offset == EDID_NO_DATA)) { + LOGHDMILIB("%s", "No video block"); + return EDIDREAD_NOVIDEO; + } + + /* Check Audio support */ + if (*(data + EDID_BL1_AUDIO_SUPPORT_OFFSET) & + EDID_BASIC_AUDIO_SUPPORT_MASK) { + *basic_audio_support = 1; + } + + for (edidp = EDID_BLK_START; edidp < offset; + edidp = edidp + length + 1) { + code = (*(data + edidp) & EDID_BLK_CODE_MSK) >> + EDID_BLK_CODE_SHIFT; + length = *(data + edidp) & EDID_BLK_LENGTH_MSK; + + if ((offset + length) >= EDIDREAD_SIZE) + return EDIDREAD_FAIL; + + LOGHDMILIB2("code:%d blklen:%d", code, length); + + switch (code) { + case EDID_CODE_VIDEO: + for (blockp = edidp + 1; blockp < edidp + 1 + length; + blockp++) { + ceanr = *(data + blockp) & EDID_SVD_ID_MASK; + LOGHDMILIB2("try ceanr:%d", ceanr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 1) && + (formats[cnt].vesaceanr == + ceanr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("cea:%d", ceanr); + break; + } + } + } + break; + + case EDID_CODE_VSDB: + p = data + edidp; + if (length >= (EDID_VSD_PHYS_SRC + 1)) { + LOGHDMILIB("source physaddr:%02x%02x", + *(p + EDID_VSD_PHYS_SRC), + *(p + EDID_VSD_PHYS_SRC + 1)); + + /*TODO logical addr (HDMI spec p.192)*/ + } + + /* Video and Audio latency */ + if ((length >= EDID_VSD_AUD_LAT) && + (*(p + EDID_VSD_LATENCY_IND) & + EDID_VSD_LAT_FLD_MASK )) { + edid_latency->video_latency = + 2 * (*(p + EDID_VSD_VID_LAT) - 1); + edid_latency->audio_latency = + 2 * (*(p + EDID_VSD_AUD_LAT) - 1); + } + + /* Interlaced Video and Audio latency */ + if ((length >= EDID_VSD_INTLCD_AUD_LAT) && + (*(p + EDID_VSD_LATENCY_IND) & + EDID_VSD_INTLCD_LAT_FLD_MASK )) { + edid_latency->intlcd_video_latency = + 2 * (*(p + EDID_VSD_INTLCD_VID_LAT) - 1); + edid_latency->audio_latency = + 2 * (*(p + EDID_VSD_INTLCD_AUD_LAT) - 1); + } + break; + + default: + break; + } + } + + /* Read Established Timing 3 and set sink_support */ + for (index = 0; index <= 2; index++) { + edidp = edid_esttim3_flag_offset[index]; + + /* Check for Established Timing3 flag */ + if (memcmp(data + edidp, edid_esttim3_flag, + sizeof(edid_esttim3_flag)) != 0) + /* Flag mismatch, this is not Established Timing 3 */ + continue; + + for (byte = EDID_BL1_ESTTIM3_BYTE_START; + byte <= EDID_BL1_ESTTIM3_BYTE_END; byte++) { + est_timing3 = *(data + edidp + byte); + for (bit = 7; bit >= 0; bit--) { + if ((est_timing3 & (1 << bit)) == 0) + /* Not supported in sink */ + continue; + + vesa_nr = get_vesanr_from_est_timing(3, byte, + bit); + /* Set sink_suuport */ + LOGHDMILIB2("EstTim3 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("EstTim3 vesa_nr:%d", + vesa_nr); + break; + } + } + } + } + } + + /* Read Standard Timings 9-16 and set sink_support*/ + for (index2 = 0; index2 <= 2; index2++) { + edidp = edid_stdtim9_flag_offset[index2]; + + /* Check for Standard Timing flag */ + if (memcmp(data + edidp, edid_stdtim9_flag, + sizeof(edid_stdtim9_flag)) != 0) + /* Flag mismatch, this is not Standard Timing 9-16 */ + continue; + + for (index = 0; index < EDID_BL1_STDTIM9_SIZE; index++) { + edidp += EDID_BL1_STDTIM9_BYTE_START + index * 2; + xres = (*(data + edidp) + 31) * 8; + byte = *(data + edidp + 1); + ar_index = (byte * EDID_STDTIM_AR_MASK) >> + EDID_STDTIM_AR_SHIFT; + yres = xres * edid_stdtim_ar[ar_index].y / + edid_stdtim_ar[ar_index].x; + freq = (byte * EDID_STDTIM_FREQ_MASK) >> + EDID_STDTIM_FREQ_SHIFT; + vesa_nr = get_vesanr_from_std_timing(xres, yres, freq); + LOGHDMILIB2("StdTim9to16 try vesa_nr:%d", vesa_nr); + for (cnt = 0; cnt < nr_formats; cnt++) { + LOGHDMILIB3("with:%d", + formats[cnt].vesaceanr); + if ((formats[cnt].cea == 0) && + (formats[cnt].vesaceanr == + vesa_nr)) { + formats[cnt].sink_support = 1; + LOGHDMILIB("StdTim9to16 %d vesa_nr:%d", + index + 1, + vesa_nr); + break; + } + } + } + } + + return RESULT_OK; +} + +/* Get EDID message of specified block and send it on client socket */ +int edidreq(__u8 block, __u32 cmd_id) +{ + int res = 0; + int ret = 0; + int edidsize = 0; + int val; + __u8 buf[512]; + __u8 ediddata[EDIDREAD_SIZE]; + + LOGHDMILIB("%s begin", __func__); + + /* Request EDID */ + res = edid_read(block, ediddata); + if (res == 0) + edidsize = EDIDREAD_SIZE; + + val = HDMI_EDIDRESP; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = edidsize + 1; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + buf[CMDBUF_OFFSET] = res; + memcpy(&buf[CMDBUF_OFFSET + 1], ediddata, edidsize); + + /* Send on socket */ + ret = clientsocket_send(buf, CMDBUF_OFFSET + val); + + LOGHDMILIB("%s end", __func__); + return ret; +} diff --git a/src/hdcp.c b/src/hdcp.c new file mode 100644 index 0000000..170afbb --- /dev/null +++ b/src/hdcp.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* String handling */ +#include +#include +#include +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +const __u8 hdcp_auth_start_val[] = {0x01, 0x00}; /* Start authentication */ +const __u8 hdcp_encr_start_val[] = {0x03, 0x01}; /* Start encryption */ +const __u8 hdcp_encr_stop_val[] = {0x00, 0x00}; /* Stop encryption */ +const __u8 hdcp_even_val[] = {0x01}; /* Enable HDCP events */ + +static char *dbg_otp(int value) +{ + switch (value) { + case OTP_UNPROGGED: + return "OTP IS NOT PROGRAMMED"; + break; + case OTP_PROGGED: + return "OTP IS PROGRAMMED"; + break; + default: + return "OTP status UNKNOWN"; + break; + } +} + +static char *dbg_loadaes(int value) +{ + switch (value) { + case LOADAES_OK: + return "LOAD AES OK\n"; + break; + case LOADAES_NOT_OK: + return "LOAD AES FAILED\n"; + break; + case LOADAES_NOT_FUSED: + return "LOAD AES FAILED NOT FUSED\n"; + break; + case LOADAES_CRC_MISMATCH: + return "LOAD AES FAILED CRC MISMATCH\n"; + break; + default: + return "LOAD AES result UNKNOWN\n"; + break; + } +} + +static char *dbg_hdcpstate(int value) +{ + switch (value) { + case HDCP_STATE_NO_RECV: + return "HDCP STATE NO_RECV"; + break; + case HDCP_STATE_RECV_CONN: + return "HDCP STATE RECV_CONN"; + break; + case HDCP_STATE_NO_HDCP: + return "HDCP STATE NO_HDCP"; + break; + case HDCP_STATE_NO_ENCR: + return "HDCP STATE NO_ENCR"; + break; + case HDCP_STATE_AUTH_ONGOING: + return "HDCP STATE AUTH_ONGOING"; + break; + case HDCP_STATE_AUTH_FAIL: + return "HDCP STATE AUTH_FAIL"; + break; + case HDCP_STATE_AUTH_SUCCEDED: + return "HDCP STATE AUTH_SUCCEDED"; + break; + case HDCP_STATE_ENCR_ONGOING: + return "HDCP STATE ENCR_ONGOING"; + break; + default: + return "HDCP STATE UNKNOWN"; + break; + } +} + +/* Load aes keys and start hdcp encryption */ +int hdcp_init(__u8 *aes) +{ + int hdcpchkaesotp; + int hdcploadaes; + int hdcpauthencr; + int hdcpeven; + int res; + int value = 0; + char buf[128]; + int result = HDCP_OK; + int events; + + /* Check if OTP is fused */ + hdcpchkaesotp = open(HDCPCHKAESOTP_FILE, O_RDONLY); + if (hdcpchkaesotp < 0) { + LOGHDMILIB("***** Failed to open %s *****", HDCPCHKAESOTP_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + res = read(hdcpchkaesotp, buf, sizeof(buf)); + close(hdcpchkaesotp); + if (res != 1) { + LOGHDMILIB("***** %s read error *****", HDCPCHKAESOTP_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + value = *buf; + LOGHDMILIB("%s", dbg_otp(value)); + + if (value == OTP_PROGGED) { + /* Subscribe for hdcp events */ + hdcpeven = open(HDCPEVEN_FILE, O_WRONLY); + if (hdcpeven < 0) { + LOGHDMILIB("***** Failed to open %s *****", + HDCPEVEN_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + write(hdcpeven, hdcp_even_val, sizeof(hdcp_even_val)); + close(hdcpeven); + + /* Write aes keys */ + hdcploadaes = open(HDCPLOADAES_FILE, O_WRONLY); + if (hdcploadaes < 0) { + LOGHDMILIB("***** Failed to open %s *****", + HDCPLOADAES_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + res = write(hdcploadaes, aes, AES_KEYS_SIZE); + close(hdcploadaes); + if (res != AES_KEYS_SIZE) { + LOGHDMILIB("***** Failed to write hdcploadaes %d " + "*****", res); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + + usleep(LOADAES_WAITTIME); + + /* Check result */ + hdcploadaes = open(HDCPLOADAES_FILE, O_RDONLY); + if (hdcploadaes < 0) { + LOGHDMILIB("***** Failed to open %s *****", + HDCPLOADAES_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + res = read(hdcploadaes, buf, sizeof(buf)); + close(hdcploadaes); + if (res != 1) { + LOGHDMILIB("***** %s read error *****", + HDCPLOADAES_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_end; + } + value = *buf; + LOGHDMILIB("%s", dbg_loadaes(value)); + if (value == LOADAES_OK) { + LOGHDMILIB("%s", "--- LOAD AES keys OK ---"); + } else { + result = AESKEYS_FAIL; + goto hdcp_end; + } + + usleep(LOADAES_WAITTIME); + + /* Start HDCP encryption */ + hdcpauthencr = open(HDCPAUTH_FILE, O_WRONLY); + if (hdcpauthencr < 0) { + LOGHDMILIB("***** Failed to open %s *****", + HDCPAUTH_FILE); + result = HDCPAUTHENCR_FAIL; + goto hdcp_end; + } + res = write(hdcpauthencr, hdcp_encr_start_val, + sizeof(hdcp_encr_start_val)); + close(hdcpauthencr); + if (res != sizeof(hdcp_encr_start_val)) { + LOGHDMILIB("***** Failed to write hdcpauthencr %d " + "*****", res); + result = HDCPAUTHENCR_FAIL; + goto hdcp_end; + } + usleep(HDCPAUTH_WAITTIME); + + } else { + printf("***** Missing aes file or HDCP AES OTP is not fused." + " *****\n"); + } + +hdcp_end: + return result; +} + +/* Get current hdcp state */ +int hdcp_state(void) +{ + int hdcpstateget; + int result = HDCP_OK; + int res; + __u8 buf[128]; + int val; + __u32 cmd_id; + + cmd_id = get_new_cmd_id_ind(); + + /* Check hdcpstate */ + hdcpstateget = open(HDCPSTATEGET_FILE, O_RDONLY); + if (hdcpstateget < 0) { + LOGHDMILIB("***** Failed to open %s *****", + HDCPSTATEGET_FILE); + result = SYSFS_FILE_FAILED; + goto hdcp_state_end; + } + res = read(hdcpstateget, buf, sizeof(buf)); + close(hdcpstateget); + if (res != 1) { + LOGHDMILIB("***** %s read error *****", + HDCPSTATEGET_FILE); + result = HDCPSTATE_FAIL; + goto hdcp_state_end; + } + + val = HDMI_HDCPSTATE; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = 1; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + memcpy(&buf[CMDBUF_OFFSET], buf, val); + + /* Send on socket */ + if (clientsocket_send(buf, CMDBUF_OFFSET + val) != 0) + result = HDCPSTATE_FAIL; + + LOGHDMILIB("%s", dbg_hdcpstate(val)); + +hdcp_state_end: + return result; +} diff --git a/src/hdmi_service.c b/src/hdmi_service.c new file mode 100644 index 0000000..2542566 --- /dev/null +++ b/src/hdmi_service.c @@ -0,0 +1,1104 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* POSIX Threads */ +#include /* String handling */ +#include +#include +#include +#include +#include "linux/fb.h" +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +pthread_t thread_main; +pthread_t thread_kevent; +pthread_t thread_socklisten; +pthread_mutex_t event_mutex; +pthread_mutex_t fb_state_mutex; +pthread_mutex_t cmd_mutex; +pthread_cond_t event_cond; +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +void (*hdmi_callback_fn)(int cmd, int data_length, __u8 *data) = NULL; +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ +int hdmi_events; +enum hdmi_fb_state hdmi_fb_state; +enum hdmi_plug_state hdmi_plug_state; +struct cmd_data *cmd_data; +int cmd_id_ind; + +const __u8 plugdetdis_val[] = {0x00, 0x00, 0x00};/* 00: disable, 00:ontime, + 00: offtime*/ +const __u8 plugdeten_val[] = {0x01, 0x05, 0x02};/* 01: enable, 05:ontime, + 02: offtime*/ + +int get_new_cmd_id_ind(void) +{ + cmd_id_ind++; + return cmd_id_ind; +} + +/* Sets the format to be used in sysfs files */ +static int storeastext(int as_text) +{ + int storeastext; + int result = RESULT_OK; + int wr_res; + char *str; + + if (as_text) + str = STOREASTEXT_STR; + else + str = STOREASBIN_STR; + + /* Set file format in sysfs files; hextext or binary */ + storeastext = open(STOREASTEXT_FILE, O_WRONLY); + if (storeastext < 0) { + LOGHDMILIB("***** Failed to open %s *****", STOREASTEXT_FILE); + result = SYSFS_FILE_FAILED; + goto storeastext_end; + } + wr_res = write(storeastext, str, strlen(str)); + close(storeastext); + if (wr_res != (int)strlen(str)) + result = STOREAS_FAIL; + +storeastext_end: + return result; +} + +/* Trigger event in kernel event file */ +static int hdmievwakeupfile_wr(void) +{ + int evwakeup; + int res; + __u8 val = 1; + + evwakeup = open(EVWAKEUP_FILE, O_WRONLY); + if (evwakeup < 0) { + LOGHDMILIB("***** Failed to open %s *****", EVWAKEUP_FILE); + return -1; + } + res = write(evwakeup, &val, 1); + close(evwakeup); + if (res != 1) { + LOGHDMILIB("***** Failed to write %s *****", EVWAKEUP_FILE); + return -2; + } + return 0; +} + +/* Set hw power */ +int poweronoff(__u8 onoff) +{ + int pwrfd; + + pwrfd = open(POWERONOFF_FILE, O_WRONLY); + if (pwrfd < 0) { + LOGHDMILIB(" failed to open %s", POWERONOFF_FILE); + return -1; + } + write(pwrfd, &onoff, 1); + close(pwrfd); + + return 0; +} + +/* Get hw power */ +static int powerstate_get(enum hdmi_power_state *power_state) +{ + int pwrfd; + int res; + __u8 onoff; + + *power_state = HDMI_POWERUNDEF; + pwrfd = open(POWERONOFF_FILE, O_RDONLY); + if (pwrfd < 0) { + LOGHDMILIB(" failed to open %s", POWERONOFF_FILE); + return -1; + } + res = read(pwrfd, &onoff, 1); + close(pwrfd); + if (res != 1) + return -1; + if (onoff) + *power_state = HDMI_POWERON; + else + *power_state = HDMI_POWEROFF; + return 0; +} + +/* Set local plug state */ +static int plugstate_set(enum hdmi_plug_state plug_state) +{ + hdmi_plug_state = plug_state; + return 0; +} + +/* Get local plug state */ +static int plugstate_get(enum hdmi_plug_state *plug_state) +{ + *plug_state = hdmi_plug_state; + return 0; +} + +/* Select HDMI or DVI mode */ +static int hdmi_format_set(enum hdmi_format format) +{ + int fd; + + fd = open(HDMIFORMAT_FILE, O_WRONLY); + if (fd < 0) { + LOGHDMILIB(" failed to open %s", HDMIFORMAT_FILE); + return -1; + } + write(fd, &format, 1); + close(fd); + + return 0; +} + +/* Send illegal state message on client socket */ +static int illegalstate_send(__u32 cmd, __u32 cmd_id) +{ + int val; + __u8 buf[16]; + int sock; + + val = cmd; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = 0; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + + /* Send on socket */ + return clientsocket_send(buf, CMDBUF_OFFSET + val); +} + +/* Subscribe for plug events */ +int hdmiplug_subscribe(void) +{ + int plugdetenfd; + int res; + + plugdetenfd = open(PLUGDETEN_FILE, O_WRONLY); + if (plugdetenfd < 0) { + LOGHDMILIB(" failed to open %s", PLUGDETEN_FILE); + goto hdmiplug_subscribe_err2; + } + + /* Unsubscribe */ + res = write(plugdetenfd, plugdetdis_val, sizeof(plugdetdis_val)); + if (res != sizeof(plugdetdis_val)) + goto hdmiplug_subscribe_err1; + + /* Subscribe */ + res = write(plugdetenfd, plugdeten_val, sizeof(plugdeten_val)); + if (res != sizeof(plugdeten_val)) + goto hdmiplug_subscribe_err1; + + close(plugdetenfd); + return 0; + +hdmiplug_subscribe_err1: + close(plugdetenfd); +hdmiplug_subscribe_err2: + return -1; +} + +/* Allow-Avoid Early suspend */ +static int stayalive(__u8 enable) +{ + int stayalivefd; + int cnt = 0; + int res; + + stayalivefd = open(STAYALIVE_FILE, O_WRONLY); + while ((stayalivefd < 0) && (cnt++ < 30)) { + usleep(200000); + stayalivefd = open(STAYALIVE_FILE, O_WRONLY); + } + LOGHDMILIB("cnt:%d", cnt); + + if (stayalivefd < 0) { + LOGHDMILIB(" failed to open %s", STAYALIVE_FILE); + goto stayalive_err2; + } + res = write(stayalivefd, &enable, 1); + if (res != 1) + goto stayalive_err1; + + close(stayalivefd); + return 0; + +stayalive_err1: + close(stayalivefd); +stayalive_err2: + return -1; +} + +/* Handling of plug events */ +static int hdmiplugged_handle(int *basic_audio_support) +{ + __u8 data[128]; + int nr_formats; + __u8 cea; + __u8 vesaceanr; + int disponoff; + char req_str[7]; + int wr_res; + int fd; + char fbname[128]; + char buf[128]; + int read_res; + struct video_format *formats; + __u8 extension; + int cnt = 0; + struct edid_latency edid_latency = {-1, -1, -1, -1}; + int res; + int ret = 0; + + LOGHDMILIB("%s", "HDMIEVENT_HDMIPLUGGED"); + + plugstate_set(HDMI_PLUGGED); + *basic_audio_support = 0; + video_formats_clear(); + + /* Behaviour at early suspend */ + stayalive(HDMI_SERVICE_STAY_ALIVE_DURING_SUSPEND); + + /* Set hdmi fb state */ + hdmi_fb_state = HDMI_FB_OPENED; + + /* Get HW supported formats */ + video_formats_supported_hw(); + nr_formats = nr_formats_get(); + formats = video_formats_get(); + + cnt = 0; + /* Read and parse EDID */ + res = -1; + while (res && (cnt < 3)) { + res = edid_read(0, data); + if (res == 0) + res = edid_parse0(data + 1, &extension, formats, nr_formats); + if (res && (cnt < 2)) + usleep(EDIDREAD_WAITTIME); + cnt++; + } + if (res) { + ret = -1; + goto hdmiplugged_handle_end; + } + + if (extension) { + /* Extension data exists */ + if (edid_read(1, data) != 0) { + ret = -2; + goto hdmiplugged_handle_end; + } + + edid_parse1(data + 1, formats, nr_formats, + basic_audio_support, &edid_latency); + + /* Set hdmi format to hdmi */ + hdmi_format_set(HDMI_FORMAT_HDMI); + cea = 1; + } else { + /* Set hdmi format to dvi */ + hdmi_format_set(HDMI_FORMAT_DVI); + cea = 0; + } + + LOGHDMILIB("Basic audio support: %d", *basic_audio_support); + LOGHDMILIB("Latency: video:%d audio:%d", + edid_latency.video_latency, + edid_latency.audio_latency); + LOGHDMILIB("Interlaced latency: video:%d audio:%d", + edid_latency.intlcd_video_latency, + edid_latency.intlcd_audio_latency); + + set_vesacea_prio_all(); + get_best_videoformat(&cea, &vesaceanr); + + /* Check if fb is created */ + /* Get fb dev name */ + disponoff = open(DISPONOFF_FILE, O_RDWR); + if (disponoff < 0) { + LOGHDMILIB("***** Failed to open %s *****", DISPONOFF_FILE); + ret = -3; + goto hdmiplugged_handle_end; + } + read_res = read(disponoff, buf, sizeof(buf)); + if (read_res > 0) { + LOGHDMILIB("fbname:%s", buf); + } else { + /* Create frame buffer with best resolution */ + lseek(disponoff, 0, SEEK_SET); + sprintf(req_str, "%02x%02x%02x", 1, cea, vesaceanr); + LOGHDMILIB("req_str:%s", req_str); + + wr_res = write(disponoff, req_str, strlen(req_str)); + if (wr_res != (int)strlen(req_str)) { + LOGHDMILIB("***** Failed to write %s *****", + DISPONOFF_FILE); + close(disponoff); + ret = -4; + goto hdmiplugged_handle_end; + } + + /* Check that fb was created */ + /* Get fb dev name */ + lseek(disponoff, 0, SEEK_SET); + read_res = read(disponoff, buf, sizeof(buf)); + if (read_res <= 0) { + LOGHDMILIB("***** Failed to read %s *****", DISPONOFF_FILE); + close(disponoff); + ret = -5; + goto hdmiplugged_handle_end; + } + + LOGHDMILIB("fbname:%s", buf); + } + close(disponoff); + + /* Change resolution to be sure to have correct freq */ + hdmi_fb_chres(cea, vesaceanr); + +hdmiplugged_handle_end: + LOGHDMILIB("%s end:%d", __func__, ret); + return ret; +} + +static int hdmiunplugged_handle(void) +{ + enum hdmi_power_state power_state; + + LOGHDMILIB("%s", "HDMIEVENT_HDMIUNPLUGGED"); + plugstate_set(HDMI_UNPLUGGED); + + /* Allow early suspend */ + stayalive(0); + return 0; +} + +static int hdmiunknownhandle(int event) +{ + LOGHDMILIB("HDMIEVENT_EVENTUNKNOWN: %d", event); + return 0; +} + +/* Close frame buffer */ +static int hdmi_fb_close(void) +{ + int disponoff; + char req_str[7]; + int wr_res; + + LOGHDMILIB("%s begin", __func__); + LOGHDMILIB("hdmi_fb_state:%d", hdmi_fb_state); + + if (hdmi_fb_state == HDMI_FB_CLOSED) { + LOGHDMILIB("%s", "FB already closed"); + return 0; + } + + /* Destroy frame buffer */ + disponoff = open(DISPONOFF_FILE, O_WRONLY); + if (disponoff < 0) { + LOGHDMILIB("***** Failed to open %s *****", DISPONOFF_FILE); + } else { + sprintf(req_str, "%02x%02x%02x", 0, 0, 0); + LOGHDMILIB("req_str:%s", req_str); + + wr_res = write(disponoff, req_str, strlen(req_str)); + close(disponoff); + if (wr_res != (int)strlen(req_str)) + LOGHDMILIB("***** Failed to write %s *****", + DISPONOFF_FILE); + } + + hdmievclr(EVENTMASK_ALL); + + hdmi_fb_state = HDMI_FB_CLOSED; + LOGHDMILIB("%s end", __func__); + return 0; +} + +/* Send Infoframe */ +static int infofr_send(__u8 type, __u8 ver, __u8 crc, __u8 len, __u8 *data) +{ + int infofrfd; + char buf[128]; + int res = 0; + + LOGHDMILIB("%s begin", __func__); + + buf[0] = type; + buf[1] = ver; + buf[2] = crc; + buf[3] = len; + memcpy(&buf[4], data, len); + + infofrfd = open(INFOFRSEND_FILE, O_WRONLY); + if (infofrfd <= 0) { + LOGHDMILIB("***** Failed to open %s *****\n", INFOFRSEND_FILE); + res = -1; + goto infofr_send_end; + } + + res = write(infofrfd, buf, len + 4); + if (res != len + 4) { + LOGHDMILIB("***** infofrsend failed %d *****\n", res); + res = -1; + goto infofr_send_end; + } + + if (infofrfd > 0) + close(infofrfd); + +infofr_send_end: + LOGHDMILIB("%s end:%d", __func__, res); + return res; +} + +/* Send plug event message on client socket */ +static int plugevent_send(__u32 cmd, int audio_support, int nr, struct vesacea vesacea[]) +{ + int res = 0; + int val; + __u8 buf[128]; + int sock; + __u32 cmd_id; + int cnt; + + LOGHDMILIB("%s begin", __func__); + + LOGHDMILIB("audio_support:%d", audio_support); + LOGHDMILIB("nr video supp:%d", nr); + + cmd_id = get_new_cmd_id_ind(); + + val = cmd; + memcpy(&buf[CMD_OFFSET], &val, 4); + memcpy(&buf[CMDID_OFFSET], &cmd_id, 4); + val = 2 + nr * 2; + memcpy(&buf[CMDLEN_OFFSET], &val, 4); + buf[CMDLEN_OFFSET] = audio_support; + buf[CMDLEN_OFFSET + 1] = nr; + for (cnt = 0; cnt < nr; cnt++) { + buf[CMDLEN_OFFSET + 2 + cnt * 2] = vesacea[cnt].cea; + buf[CMDLEN_OFFSET + 3 + cnt * 2] = vesacea[cnt].nr; + } + + /* Send on socket */ + res = clientsocket_send(buf, CMDBUF_OFFSET + val); + LOGHDMILIB("%s end", __func__); + return res; +} + +/* Add command to list */ +int cmd_add(struct cmd_data *cmd) +{ + struct cmd_data **cmd_obj; + struct cmd_data *cmd_new; + + LOGHDMILIB("%s begin", __func__); + + cmd_new = malloc(sizeof(struct cmd_data)); + if (cmd_new) + memcpy(cmd_new, cmd, sizeof(struct cmd_data)); + else + return -1; + + /* Add to list */ + pthread_mutex_lock(&cmd_mutex); + cmd_obj = &cmd_data; + while (*cmd_obj != NULL) + cmd_obj = &((*cmd_obj)->next); + *cmd_obj = cmd_new; + pthread_mutex_unlock(&cmd_mutex); + + LOGHDMILIB("%s end", __func__); + return 0; +} + +/* Delete command list */ +static int cmd_del_all(void) +{ + struct cmd_data **cmd_obj; + struct cmd_data *cmd_del; + + LOGHDMILIB("%s begin", __func__); + pthread_mutex_lock(&cmd_mutex); + cmd_obj = &cmd_data; + while (*cmd_obj != NULL) { + cmd_del = *cmd_obj; + cmd_obj = &((*cmd_obj)->next); + free(cmd_del); + } + pthread_mutex_unlock(&cmd_mutex); + LOGHDMILIB("%s end", __func__); + return 0; +} + +/* Signal an event to main thread */ +int hdmi_event(int event) +{ + pthread_mutex_lock(&event_mutex); + hdmi_events |= event; + if (hdmi_events) + pthread_cond_signal(&event_cond); + pthread_mutex_unlock(&event_mutex); + return 0; +} + +/* Handling of received command */ +static int hdmi_eventcmd(void) +{ + struct cmd_data *cmd_obj = NULL; + struct cmd_data *del_obj; + int res = 0; + int ret = -1; + enum hdmi_power_state power_state; + enum hdmi_plug_state plug_state; + int handlecmd; + + LOGHDMILIB("%s begin", __func__); + + pthread_mutex_lock(&cmd_mutex); + if (cmd_data) { + cmd_obj = cmd_data; + cmd_data = cmd_data->next; + } + pthread_mutex_unlock(&cmd_mutex); + + /* Handle all mesages in list */ + while (cmd_obj) { + res = -1; + handlecmd = 1; + ret = cmd_obj->cmd; + + /* Check power and plug state */ + switch (cmd_obj->cmd) { + case HDMI_ENABLE: + case HDMI_DISABLE: + case HDMI_EXIT: + case HDMI_CECSEND: + default: + break; + + case HDMI_EDIDREQ: + case HDMI_FB_RES_SET: + case HDMI_HDCP_INIT: + case HDMI_VESACEAPRIO_SET: + case HDMI_INFOFR: + handlecmd = 0; + powerstate_get(&power_state); + plugstate_get(&plug_state); + if (power_state != HDMI_POWERON) + illegalstate_send(HDMI_ILLSTATE_UNPOWERED, + cmd_obj->cmd_id); + else if (plug_state != HDMI_PLUGGED) + illegalstate_send(HDMI_ILLSTATE_UNPLUGGED, + cmd_obj->cmd_id); + else + handlecmd = 1; + break; + + case HDMI_FB_RELEASE: + handlecmd = 0; + powerstate_get(&power_state); + plugstate_get(&plug_state); + if ((power_state == HDMI_POWERON) && + (plug_state == HDMI_PLUGGED)) + illegalstate_send(HDMI_ILLSTATE_PWRON_PLUGGED, + cmd_obj->cmd_id); + else + handlecmd = 1; + break; + } + + if (handlecmd == 0) + break; + + /* Handle cmd */ + switch (cmd_obj->cmd) { + case HDMI_ENABLE: + /* Subscribe on plug events */ + hdmiplug_subscribe(); + + /* Subscribe for cec events */ + res = cecrx_subscribe(); + break; + + case HDMI_DISABLE: + res = hdmi_fb_close(); + break; + + case HDMI_EDIDREQ: + res = edidreq(cmd_obj->data[0], cmd_obj->cmd_id); + break; + + case HDMI_CECSEND: + res = cecsend(cmd_obj->cmd_id, + cmd_obj->data[0], + cmd_obj->data[1], + cmd_obj->data[2], + &cmd_obj->data[3]); + break; + + case HDMI_FB_RES_SET: + res = hdmi_fb_chres(cmd_obj->data[0], cmd_obj->data[1]); + break; + + case HDMI_FB_RELEASE: + hdmi_fb_close(); + + /* Subscribe on plug events */ + hdmiplug_subscribe(); + + /* Subscribe for cec events */ + res = cecrx_subscribe(); + break; + + case HDMI_HDCP_INIT: + if (cmd_obj->data_len != (AES_KEYS_SIZE + 12)) + res = -1; + else + res = hdcp_init(cmd_obj->data); + break; + + case HDMI_VESACEAPRIO_SET: + res = vesaceaprio_set(cmd_obj->data[0], + &cmd_obj->data[1]); + break; + + case HDMI_INFOFR: + res = infofr_send(cmd_obj->data[0], + cmd_obj->data[1], + cmd_obj->data[2], + cmd_obj->data[3], + &cmd_obj->data[4]); + break; + + case HDMI_EXIT: + hdmi_fb_close(); + res = 0; + + /* delete list */ + cmd_del_all(); + + hdmievwakeupfile_wr(); + + goto hdmi_eventcmd_end; + break; + + default: + break; + } + + LOGHDMILIB("cmd_id:%x res:%d\n", cmd_obj->cmd_id, res); + + free(cmd_obj); + + pthread_mutex_lock(&cmd_mutex); + if (cmd_data) { + cmd_obj = cmd_data; + cmd_data = cmd_data->next; + } else + cmd_obj = NULL; + pthread_mutex_unlock(&cmd_mutex); + } + +hdmi_eventcmd_end: + LOGHDMILIB("%s end", __func__); + + if (res != 0) + ret = res; + + return ret; +} + +static int hdmi_service_exit_do(void) +{ + int sock; + int res; + + LOGHDMILIB("%s begin", __func__); + + /* Shutdown listen socket to end listen thread */ + sock = listensocket_get(); + listensocket_set(-1); + res = shutdown(sock, SHUT_RDWR); + + pthread_mutex_destroy(&event_mutex); + pthread_mutex_destroy(&cmd_mutex); + pthread_mutex_destroy(&fb_state_mutex); + pthread_cond_destroy(&event_cond); + + LOGHDMILIB("%s end", __func__); + return res; +} + +/* Main thread. Handles messages from client thread or kernel event thread */ +static void thread_main_fn(void *arg) +{ + int events; + int cont = 1; + int dummy = 0; + int res; + int audio_support; + int nr_video; + struct vesacea video_supported[FORMATS_MAX]; + + LOGHDMILIB("%s begin", __func__); + + pthread_create(&thread_kevent, NULL, (void *)thread_kevent_fn, + (void *)&dummy); + + pthread_create(&thread_socklisten, NULL, (void *)thread_socklisten_fn, + (void *)&dummy); + + while (cont) { + /* Wait for event */ + pthread_mutex_lock(&event_mutex); + if (hdmi_events == 0) + /* Wait only if there are no events pending. + * event_mutex is automatically unlocked while waiting + * and locked again when thread is awakened. + */ + pthread_cond_wait(&event_cond, &event_mutex); + events = hdmi_events; + hdmi_events = 0; + pthread_mutex_unlock(&event_mutex); + + LOGHDMILIB("%s: event:%x", __func__, events); + + /* kernel events */ + if (events & HDMIEVENT_HDMIPLUGGED) { + if (hdmiplugged_handle(&audio_support) == 0) { + vesacea_supported(&nr_video, video_supported); + plugevent_send(HDMI_PLUGGED_EV, audio_support, + nr_video, + video_supported); + } + } else if (events & HDMIEVENT_HDMIUNPLUGGED) { + hdmiunplugged_handle(); + plugevent_send(HDMI_UNPLUGGED_EV, 0, 0, NULL); + } + + if (events & HDMIEVENT_CEC) + cecrx(); + if (events & HDMIEVENT_HDCP) + hdcp_state(); + if (events & HDMIEVENT_CECTXERR) + cecsenderr(); + + /* App cmd event */ + if (events & HDMIEVENT_CMD) { + res = hdmi_eventcmd(); + if (res == HDMI_EXIT) { + cont = 0; + /* Wait for kevent thread to exit */ + usleep(2000000); + } + } + } + + pthread_mutex_lock(&event_mutex); + hdmi_events = 0; + pthread_mutex_unlock(&event_mutex); + + hdmi_service_exit_do(); + + LOGHDMILIB("%s end", __func__); + + /* Exit thread */ + pthread_exit(NULL); +} + +/* API helper functions */ +int hdmi_service_init(int avoid_return_msg) +{ + int dummy = 0; + __u8 ceavesadata[2]; + int socket; + + LOGHDMILIB("%s begin", __func__); + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN + hdmi_service_callback_set(NULL); +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + + /* Set sysfs format to binary */ + storeastext(0); + + ceavesadata[0] = 0; + ceavesadata[1] = 0; + LOGHDMILIB("desired cea:%d nr:%d", ceavesadata[0], ceavesadata[1]); + vesaceaprio_set(1, ceavesadata); + + pthread_mutex_init(&event_mutex, NULL); + pthread_mutex_init(&cmd_mutex, NULL); + pthread_mutex_init(&fb_state_mutex, NULL); + pthread_cond_init(&event_cond, NULL); + + /* Create threads */ + pthread_create(&thread_main, NULL, (void *)thread_main_fn, + (void *)&dummy); + + LOGHDMILIB("%s end", __func__); + + /* Wait for threads to start */ + usleep(100000); + socket = serversocket_create(avoid_return_msg); + + return socket; +} + +int hdmi_service_exit(void) +{ + int val; + __u8 buf[32]; + val = HDMI_EXIT; + + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 0; + memcpy(&buf[8], &val, 4); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_enable(void) +{ + int val; + __u8 buf[32]; + val = HDMI_ENABLE; + + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 0; + memcpy(&buf[8], &val, 4); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_disable(void) +{ + int val; + __u8 buf[32]; + val = HDMI_DISABLE; + + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 0; + memcpy(&buf[8], &val, 4); + serversocket_write(12 + val, buf); + + return 0; +} + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +void hdmi_service_callback_set(cb_fn hdmi_cb) +{ + hdmi_callback_fn = hdmi_cb; +} + +cb_fn hdmi_service_callback_get(void) +{ + return hdmi_callback_fn; +} +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + +int hdmi_service_resolution_set(int cea, int vesaceanr) +{ + int val; + __u8 buf[32]; + + val = HDMI_FB_RES_SET; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 2; + memcpy(&buf[8], &val, 4); + /* data */ + buf[12] = cea; + buf[13] = vesaceanr; + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_fb_release(void) +{ + int val; + __u8 buf[32]; + + val = HDMI_FB_RELEASE; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 0; + memcpy(&buf[8], &val, 4); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_cec_send(__u8 initiator, __u8 destination, __u8 data_size, + __u8 *data) +{ + int val; + __u8 buf[32]; + + if (data_size > CEC_MSG_SIZE_MAX) + return -1; + + val = HDMI_CECSEND; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = data_size + 3; + memcpy(&buf[8], &val, 4); + /* data */ + buf[12] = initiator; + buf[13] = destination; + buf[14] = data_size; + memcpy(&buf[15], data, data_size); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_edid_request(__u8 block) +{ + int val; + __u8 buf[32]; + + if (block >= 2) + return -1; + + val = HDMI_EDIDREQ; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 1; + memcpy(&buf[8], &val, 4); + /* data */ + buf[12] = block; + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_hdcp_init(__u16 aes_size, __u8 *aes_data) +{ + int val; + __u8 buf[300]; + + if (aes_size != AES_KEYS_SIZE) + return -1; + + val = HDMI_HDCP_INIT; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = aes_size; + memcpy(&buf[8], &val, 4); + /* data */ + memcpy(&buf[12], aes_data, aes_size); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_infoframe_send(__u8 type, __u8 version, __u8 crc, + __u8 data_size, __u8 *data) +{ + int val; + __u8 buf[300]; + + if (data_size > INFOFR_MSG_SIZE_MAX) + return -1; + + val = HDMI_INFOFR; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = data_size + 4; + memcpy(&buf[8], &val, 4); + /* data */ + buf[12] = type; + buf[13] = version; + buf[14] = crc; + buf[15] = data_size; + memcpy(&buf[16], data, data_size); + serversocket_write(12 + val, buf); + + return 0; +} + +int hdmi_service_vesa_cea_prio_set(__u8 vesa_cea1, __u8 nr1, + __u8 vesa_cea2, __u8 nr2, + __u8 vesa_cea3, __u8 nr3) +{ + int val; + __u8 buf[32]; + + val = HDMI_VESACEAPRIO_SET; + memcpy(&buf[0], &val, 4); + /* cmd_id */ + val = 0; + memcpy(&buf[4], &val, 4); + /* len */ + val = 7; + memcpy(&buf[8], &val, 4); + /* data */ + buf[12] = 3; + buf[13] = vesa_cea1; + buf[14] = nr1; + buf[15] = vesa_cea2; + buf[16] = nr2; + buf[17] = vesa_cea3; + buf[18] = nr3; + serversocket_write(12 + val, buf); + + return 0; +} diff --git a/src/hdmi_service_api.c b/src/hdmi_service_api.c new file mode 100644 index 0000000..42edf26 --- /dev/null +++ b/src/hdmi_service_api.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* POSIX Threads */ +#include /* String handling */ +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +/* API functions + * Input avoid_return_msg: set to 1 to avoid messages from service. + * Return value: socket number where events will be notified. + */ +int hdmi_init(int avoid_return_msg) +{ + return hdmi_service_init(avoid_return_msg); +} + +int hdmi_exit(void) +{ + return hdmi_service_exit(); +} + +int hdmi_enable(void) +{ + return hdmi_service_enable(); +} + +int hdmi_disable(void) +{ + return hdmi_service_disable(); +} + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +void hdmi_callback_set(void (*hdmi_cb)(int cmd, int data_size, __u8 *data)) +{ + hdmi_service_callback_set(hdmi_cb); +} +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + +int hdmi_resolution_set(int cea, int vesaceanr) +{ + return hdmi_service_resolution_set(cea, vesaceanr); +} + +int hdmi_fb_release(void) +{ + return hdmi_service_fb_release(); +} + +int hdmi_cec_send(__u8 initiator, __u8 destination, __u8 data_size, __u8 *data) +{ + return hdmi_service_cec_send(initiator, destination, data_size, data); +} + +int hdmi_edid_request(__u8 block) +{ + return hdmi_service_edid_request(block); +} + +int hdmi_hdcp_init(__u16 aes_size, __u8 *aes_data) +{ + return hdmi_service_hdcp_init(aes_size, aes_data); +} + +int hdmi_infoframe_send(__u8 type, __u8 version, __u8 crc, __u8 data_size, + __u8 *data) +{ + return hdmi_service_infoframe_send(type, version, crc, data_size, data); +} + +int hdmi_vesa_cea_prio_set(__u8 vesa_cea1, __u8 nr1, + __u8 vesa_cea2, __u8 nr2, + __u8 vesa_cea3, __u8 nr3) +{ + return hdmi_service_vesa_cea_prio_set(vesa_cea1, nr1, + vesa_cea2, nr2, + vesa_cea3, nr3); +} diff --git a/src/hdmi_service_start.c b/src/hdmi_service_start.c new file mode 100644 index 0000000..49c0914 --- /dev/null +++ b/src/hdmi_service_start.c @@ -0,0 +1,29 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* POSIX Threads */ +#include /* String handling */ +#include "../include/hdmi_service_api.h" + +#define NO_RETURN_MESSAGES 1 + +int main(int argc, char *argv[]) +{ + hdmi_init(NO_RETURN_MESSAGES); + + hdmi_enable(); + + pthread_exit(NULL); + + return 0; +} diff --git a/src/kevent.c b/src/kevent.c new file mode 100644 index 0000000..f6375c4 --- /dev/null +++ b/src/kevent.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* POSIX Threads */ +#include /* String handling */ +#include +#include +#include +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +static int hdmieventfile_open(struct pollfd *pollfds) +{ + + pollfds->fd = open(EVENT_FILE, O_RDONLY); + if (pollfds->fd < 0) { + LOGHDMILIB(" failed to open %s", EVENT_FILE); + return -1; + } + + pollfds->events = POLLERR | POLLPRI; + return 0; +} + +static int hdmieventfile_read(int fd) +{ + int read_res; + char buf[128]; + int events; + + read_res = read(fd, buf, sizeof(buf)); + /* seek back so we can read the new state */ + lseek(fd, 0, SEEK_SET); + + LOGHDMILIB2("read_res:%d", read_res); + + if (read_res != POLL_READ_SIZE) + return HDMIEVENT_POLLSIZEFAIL; + + events = *buf; + + LOGHDMILIB2("events read:%02x", events); + return events; +} + +static int hdmieventfile_poll(struct pollfd *pollfds) +{ + int result; + int timeout = -1; /* Timeout in msec. */ + + result = poll(pollfds, 1, timeout); + switch (result) { + case 0: + LOGHDMILIB2("%s", "timeout"); + break; + case -1: + LOGHDMILIB2("%s", "poll error"); + break; + default: + if (pollfds->revents & POLLERR) + LOGHDMILIB2("res:%d poll done", result); + else { + LOGHDMILIB2("rev:%x res:%d poll done2", + pollfds->revents, result); + } + break; + } + return 0; +} + +static int hdmieventfile_close(int fd) +{ + LOGHDMILIB("%s", __func__); + close(fd); + return 0; +} + +int hdmievclr(__u8 mask) +{ + int evclrfd; + + evclrfd = open(EVENTCLR_FILE, O_WRONLY); + if (evclrfd < 0) { + LOGHDMILIB(" failed to open %s", EVENTCLR_FILE); + return -1; + } + write(evclrfd, &mask, 1); + close(evclrfd); + return 0; +} + +/* + * Reading of kernel events + */ +void thread_kevent_fn(void *arg) +{ + int event; + struct pollfd pollfds; + + LOGHDMILIB("%s begin", __func__); + + /* + * Note: events are subscribed at call to api function hdmi_enable + * Until then no events will occur. + */ + + /* Open the event file */ + hdmieventfile_open(&pollfds); + + while (1) { + /* Read poll event file */ + event = hdmieventfile_read(pollfds.fd); + LOGHDMILIB("kevent:%x", event); + + if (event == HDMIEVENT_POLLSIZEFAIL) { + usleep(100000); + + /* Close event file */ + hdmieventfile_close(pollfds.fd); + + /* Open the event file */ + hdmieventfile_open(&pollfds); + + } else { + /* Signal main thread */ + hdmi_event(event); + + if (event & HDMIEVENT_WAKEUP) + break; + + } + + /* Poll plug event file */ + hdmieventfile_poll(&pollfds); + } + + /* Clear events */ + hdmievclr(EVENTMASK_ALL); + + /* Close event file */ + hdmieventfile_close(pollfds.fd); + + LOGHDMILIB("%s end", __func__); + + /* Exit thread */ + pthread_exit(NULL); +} diff --git a/src/setres.c b/src/setres.c new file mode 100644 index 0000000..1bf566f --- /dev/null +++ b/src/setres.c @@ -0,0 +1,400 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* String handling */ +#include +#include +#include +#include "linux/fb.h" +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +/* List of cea numbers. First ceanr has highest priority */ +struct vesacea vesaceaprio[CEAPRIO_MAX_SIZE]; + +/* + * During edid_parse, sink_support will be filled in. + * The first format in this list with sink_support set will be chosen. + */ +int video_formats_nr; +struct video_format video_formats[FORMATS_MAX]; + +int video_formats_clear(void) +{ + memset(video_formats, 0, sizeof(video_formats)); + return 0; +} + +int vesacea_supported(int *nr, struct vesacea vesacea[]) +{ + int index; + + *nr = 0; + LOGHDMILIB2("%s begin", __func__); + for (index = 0; index < FORMATS_MAX; index++) { + if (video_formats[index].sink_support) { + vesacea[*nr].cea = video_formats[index].cea; + vesacea[*nr].nr = video_formats[index].vesaceanr; + LOGHDMILIB2("cea:%d nr:%d", vesacea[*nr].cea, vesacea[*nr].nr); + (*nr)++; + } + } + LOGHDMILIB2("%s end", __func__); + return 0; +} + +int video_formats_supported_hw(void) +{ + int res; + int index; + int vesacea; + char buf[FORMATS_MAX * 2 + 1]; + + /* Get hw supported formats */ + vesacea = open(VESACEAFORMATS_FILE, O_RDONLY); + if (vesacea < 0) { + LOGHDMILIB("***** Failed to open %s *****", + VESACEAFORMATS_FILE); + return -1; + } + + res = read(vesacea, buf, sizeof(buf)); + close(vesacea); + if (res <= 0) { + LOGHDMILIB("***** Failed to read %s *****", + VESACEAFORMATS_FILE); + return -1; + } + + for (index = 0; index < FORMATS_MAX; index++) { + if ((index * 2 + 2) > res) { + /* No more to read */ + video_formats[index].cea = 0; + video_formats[index].vesaceanr = 0; + video_formats[index].sink_support = 0; + video_formats[index].prio = VESACEAPRIO_DEFAULT; + break; + } + video_formats[index].cea = *(buf + index * 2); + video_formats[index].vesaceanr = *(buf + index * 2 + 1); + video_formats[index].sink_support = 0; + video_formats[index].prio = VESACEAPRIO_DEFAULT; + } + video_formats_nr = index; + return 0; +} + +int nr_formats_get(void) +{ + return video_formats_nr; +} + +struct video_format *video_formats_get(void) +{ + return video_formats; +} + +static int vesaceanrtovar(struct fb_var_screeninfo *var, __u8 cea, + __u8 vesaceanr, __u8 num_buffers) +{ + int timing; + int res; + unsigned int index; + char buf[128]; + int interlaced; + + /* Request timing info */ + timing = open(TIMING_FILE, O_RDWR); + if (timing < 0) { + LOGHDMILIB("***** Failed to open %s *****", TIMING_FILE); + return -1; + } + buf[0] = cea; + buf[1] = vesaceanr; + res = write(timing, buf, 2); + if (res <= 0) { + LOGHDMILIB("***** Failed to write %s *****", TIMING_FILE); + close(timing); + return -1; + } + + lseek(timing, 0, SEEK_SET); + res = read(timing, buf, sizeof(buf)); + close(timing); + if (res <= 0) { + LOGHDMILIB("***** Failed to read %s *****", TIMING_FILE); + return -1; + } + + /* Read timing info */ + if (res == TIMING_SIZE) { + index = 0; + memcpy(&var->xres, buf + index, 4); + LOGHDMILIB3("xres:%d", var->xres); + index += 4; + memcpy(&var->yres, buf + index, 4); + LOGHDMILIB3("yres:%d", var->yres); + index += 4; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres * num_buffers; + memcpy(&var->pixclock, buf + index, 4); + LOGHDMILIB3("pixclock:%d", var->pixclock); + index += 4; + memcpy(&var->left_margin, buf + index, 4); + LOGHDMILIB3("left_margin:%d", var->left_margin); + index += 4; + memcpy(&var->right_margin, buf + index, 4); + LOGHDMILIB3("right_margin:%d", var->right_margin); + index += 4; + memcpy(&var->upper_margin, buf + index, 4); + LOGHDMILIB3("upper_margin:%d", var->upper_margin); + index += 4; + memcpy(&var->lower_margin, buf + index, 4); + LOGHDMILIB3("lower_margin:%d", var->lower_margin); + index += 4; + var->vmode &= ~FB_VMODE_INTERLACED; + memcpy(&interlaced, buf + index, 4); + LOGHDMILIB3("vmode:%x", var->vmode); + var->vmode |= interlaced ? FB_VMODE_INTERLACED : + FB_VMODE_NONINTERLACED; + LOGHDMILIB("CEA %d nr %d found\n", cea, vesaceanr); + return 0; + } + return -EINVAL; +} + +static void vesacea_prio_default(void) +{ + /* 1920x1080P@30 */ + vesaceaprio[0].cea = 1; + vesaceaprio[0].nr = 34; + + /* 1920x1080P@25 */ + vesaceaprio[1].cea = 1; + vesaceaprio[1].nr = 33; + + /* 1920x1080P@24 */ + vesaceaprio[2].cea = 1; + vesaceaprio[2].nr = 32; + + /* 1920x1080I@60 */ + vesaceaprio[3].cea = 1; + vesaceaprio[3].nr = 5; + + /* 1920x1080I@30 */ + vesaceaprio[4].cea = 1; + vesaceaprio[4].nr = 20; + + /* 1280x720P@60 */ + vesaceaprio[5].cea = 1; + vesaceaprio[5].nr = 4; + + /* 1280x720P@50 */ + vesaceaprio[6].cea = 1; + vesaceaprio[6].nr = 19; + + /* 720x480P@60 */ + vesaceaprio[7].cea = 1; + vesaceaprio[7].nr = 3; + + /* end of list */ + vesaceaprio[8].cea = 0; + vesaceaprio[8].nr = 0; +} + +static void set_vesacea_prio(__u8 cea, __u8 vesaceanr, __u8 prio) +{ + int nr_formats = sizeof(video_formats)/sizeof(video_formats[0]); + int index; + + for (index = 0; index < nr_formats; index++) { + if ((video_formats[index].cea == cea) & + (video_formats[index].vesaceanr == vesaceanr)) { + video_formats[index].prio = prio; + LOGHDMILIB("set_cea_prio %d %d", index, prio); + break; + } + } +} + +void set_vesacea_prio_all(void) +{ + int index; + + vesacea_prio_default(); + + /* Set cea prio. Continue until prio = 0 or maxsize */ + for (index = 0; index < CEAPRIO_MAX_SIZE; index++) { + LOGHDMILIB("index:%d cea:%d prio:%d", + index, + vesaceaprio[index].cea, + vesaceaprio[index].nr); + if (vesaceaprio[index].nr == 0) + break; + + set_vesacea_prio(vesaceaprio[index].cea, + vesaceaprio[index].nr, + index + 1); + } +} + +int get_best_videoformat(__u8 *cea, __u8 *vesaceanr) +{ + int index; + int nr_formats; + struct video_format *video_formats; + __u8 best_prio; + int best_ceanr; + int best_vesanr; + + *cea = 1; + *vesaceanr = VIDEO_FORMAT_DEFAULT; + + nr_formats = nr_formats_get(); + video_formats = video_formats_get(); + best_prio = VESACEAPRIO_DEFAULT + 1; + best_ceanr = 0; + best_vesanr = 0; + + /* Choose best video format */ + for (index = 0; index < nr_formats; index++) { + LOGHDMILIB("test cea:%d nr:%d prio:%d", + video_formats[index].cea, + video_formats[index].vesaceanr, + video_formats[index].prio); + if (video_formats[index].sink_support == 0) + /* No sink support, check next format */ + continue; + + if (video_formats[index].prio < best_prio) { + /* Prio is best */ + *cea = video_formats[index].cea; + *vesaceanr = video_formats[index].vesaceanr; + best_prio = video_formats[index].prio; + } else if (best_prio >= VESACEAPRIO_DEFAULT) { + /* Prio is not set; check ceanr */ + if (video_formats[index].cea && + (video_formats[index].vesaceanr > + best_ceanr)) { + /* It is the highest ceanr */ + *cea = 1; + *vesaceanr = video_formats[index].vesaceanr; + best_ceanr = *vesaceanr; + } + + /* If no cea has been chosen, check vesa */ + if ((best_ceanr == 0) & + (!video_formats[index].cea && + (video_formats[index].vesaceanr > + best_vesanr))) { + /* It is the highest veasanr */ + *cea = 0; + *vesaceanr = video_formats[index].vesaceanr; + best_vesanr = *vesaceanr; + } + } + LOGHDMILIB("cea:%d nr:%d best_prio:%d", + *cea, *vesaceanr, best_prio); + } + + return 0; +} + +int hdmi_fb_chres(__u8 cea, __u8 vesaceanr) +{ + struct fb_var_screeninfo var; + int fd; + char fbname[128]; + char buf[128]; + int read_res; + int disponoff; + __u8 num_buffers; + + /* Get fb dev name */ + disponoff = open(DISPONOFF_FILE, O_RDONLY); + if (disponoff < 0) { + LOGHDMILIB("***** Failed to open %s *****", DISPONOFF_FILE); + return -1; + } + + read_res = read(disponoff, buf, sizeof(buf)); + close(disponoff); + if (read_res <= 0) { + LOGHDMILIB("***** Failed to read %s *****", DISPONOFF_FILE); + return -1; + } + + /* Open fb */ + sprintf(fbname, "%s%s", FBPATH, buf); + LOGHDMILIB("fbname:%s", fbname); + fd = open(fbname, O_RDONLY); + if (fd <= 0) { + LOGHDMILIB("%s", "***** Open fb failed *****"); + return -2; + } + + /* Get screen info */ + if (ioctl(fd, FBIOGET_VSCREENINFO, &var)) { + LOGHDMILIB("%s", "***** FBIOGET_VSCREENINFO failed *****"); + close(fd); + return -3; + } + + num_buffers = var.yres_virtual / var.yres; + /* Convert ceanr to screeninfo */ + vesaceanrtovar(&var, cea, vesaceanr, num_buffers); + + /* Set screen info */ + if (ioctl(fd, FBIOPUT_VSCREENINFO, &var)) { + LOGHDMILIB("%s", "***** FBIOPUT_VSCREENINFO failed *****"); + close(fd); + return -4; + } + + /* Close fb */ + close(fd); + return 0; +} + +int vesaceaprio_set(__u8 len, __u8 *data) +{ + int index; + int index_last; + int cnt = 0; + + LOGHDMILIB("%s begin", __func__); + + if (len < CEAPRIO_MAX_SIZE) + index_last = len; + else + index_last = CEAPRIO_MAX_SIZE; + + for (index = 0; index < CEAPRIO_MAX_SIZE; index++) { + if (index < index_last) { + vesaceaprio[index].cea = data[index * 2]; + vesaceaprio[index].nr = data[index * 2 + 1]; + LOGHDMILIB("prio:%d cea:%d nr:%d", cnt, + vesaceaprio[index].cea, + vesaceaprio[index].nr); + } else { + vesaceaprio[index].cea = 0; + vesaceaprio[index].nr = 0; + break; + } + cnt++; + } + + LOGHDMILIB("%s end", __func__); + return 0; +} diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..e6256c3 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Per Persson per.xb.persson@stericsson.com for + * ST-Ericsson. + * License terms: . + */ + +#include /* Symbolic Constants */ +#include /* Primitive System Data Types */ +#include /* Errors */ +#include +#include /* Input/Output */ +#include /* General Utilities */ +#include /* POSIX Threads */ +#include /* String handling */ +#include +#include +#include +#include +#include +#include +#include "../include/hdmi_service_api.h" +#include "../include/hdmi_service_local.h" + +pthread_t thread_sockclient; +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +pthread_t thread_sockserver; +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ +int clientsocket = -1; +int listensocket = -1; +int serversocket = -1; +int no_return_msg = -1; + +int listensocket_set(int sock) +{ + listensocket = sock; + return 0; +} + +int listensocket_get(void) +{ + return listensocket; +} + +static int clientsocket_set(int sock) +{ + clientsocket = sock; + return 0; +} + +int clientsocket_get(void) +{ + return clientsocket; +} + +/* Client socket thread. Handles incoming socket messages */ +static void thread_sockclient_fn(void *arg) +{ + int res; + char buffer[SOCKET_DATA_MAX]; + struct cmd_data cmd_data; + int cont = 1; + int sock; + + LOGHDMILIB("%s begin", __func__); + + sock = (int)arg; + clientsocket_set(sock); + LOGHDMILIB("clisock:%d", sock); + + while (cont) { + memset(buffer, 0, SOCKET_DATA_MAX); + res = read(sock, buffer, SOCKET_DATA_MAX); + if (res <= 0) { + LOGHDMILIB("clisocket closed:%d", res); + goto thread_sockclient_fn_end; + } + + LOGHDMILIB("clisockread res:%d", res); + + cmd_data.cmd = (__u32)buffer[0]; + cmd_data.cmd_id = (__u32)buffer[4]; + cmd_data.data_len = (__u32)buffer[8]; + memcpy(cmd_data.data, &buffer[12], cmd_data.data_len); + cmd_data.next = NULL; + + /* Add to list */ + cmd_add(&cmd_data); + + /* Signal */ + hdmi_event(HDMIEVENT_CMD); + + if (cmd_data.cmd == HDMI_EXIT) + cont = 0; + } + +thread_sockclient_fn_end: + close(sock); + clientsocket_set(-1); + + LOGHDMILIB("%s end res:%d", __func__, res); + pthread_exit(NULL); +} + +int clientsocket_send(__u8 *buf, int len) +{ + int sock; + int sent = -1; + int res = -1; + + if (no_return_msg == 1) + return 0; + + sock = clientsocket_get(); + if (sock >= 0) { + sent = write(sock, buf, len); + LOGHDMILIB("%s written %d bytes on sock", __func__, sent); + } + + if (sent == len) + res = 0; + return res; +} + +/* Socket listen thread. + * Creates a listen socket. + * Listens for incoming connection. + * At connection attempt, creates a client socket in client thread. + */ +void thread_socklisten_fn(void *arg) +{ + int socknew; + socklen_t clilen; + struct sockaddr_un serv_addr; + struct sockaddr_un cli_addr; + int res; + fd_set descr; + struct timeval timeout; + int sockl; + + LOGHDMILIB("%s begin", __func__); + + /* Create listen socket */ + sockl = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockl < 0) { + LOGHDMILIB("%s", "socket create fail"); + goto thread_socklisten_fn_end; + } + listensocket_set(sockl); + LOGHDMILIB2("Listen socket create:%d", sockl); + + /* Remove any old path */ + unlink(SOCKET_LISTEN_PATH); + + /* Bind to path */ + memset((char *) &serv_addr, 0, sizeof(struct sockaddr_un)); + serv_addr.sun_family = AF_UNIX; + strcpy(serv_addr.sun_path, SOCKET_LISTEN_PATH); + res = bind(sockl, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); + if (res < 0) { + LOGHDMILIB("socket bind fail:%d", res); + goto thread_socklisten_fn_end; + } + + LOGHDMILIB2("Listen socket bind: %d", res); + + /* while loop is breaked by shutdown on listen socket */ + while (1) { + /* Listen for incoming connection */ + listen(sockl, SOCKET_MAX_CONN); + + /* Establish a client connection */ + clilen = sizeof(cli_addr); + socknew = accept(sockl, (struct sockaddr *) &cli_addr, &clilen); + if (socknew < 0) { + LOGHDMILIB("socket accept fail:%d", socknew); + goto thread_socklisten_fn_end; + } + LOGHDMILIB2("socket accept:%d", socknew); + + if (socknew >= 0) + /* Create a client thread */ + pthread_create(&thread_sockclient, NULL, + (void *)thread_sockclient_fn, (void *)socknew); + } + +thread_socklisten_fn_end: + /* Remove any old path */ + unlink(SOCKET_LISTEN_PATH); + + LOGHDMILIB("%s end", __func__); + pthread_exit(NULL); +} + +static int serversocket_set(int sock) +{ + serversocket = sock; + return 0; +} + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN +/* Server socket thread. Handles outgoing socket messages */ +static void thread_sockserver_fn(void *arg) +{ + int res; + char buffer[SOCKET_DATA_MAX]; + struct cmd_data cmd_data; + int cont = 1; + int sock; + cb_fn callback; + + LOGHDMILIB("%s begin", __func__); + + sock = (int)arg; + + while (cont) { + memset(buffer, 0, SOCKET_DATA_MAX); + res = read(sock, buffer, SOCKET_DATA_MAX); + if (res <= 0) { + LOGHDMILIB("servsocket closed:%d", res); + goto thread_sockserver_fn_end; + } + + LOGHDMILIB("servsockread res:%d", res); + + if (cmd_data.cmd == HDMI_EXIT) { + cont = 0; + } else { + cmd_data.cmd = (__u32)buffer[0]; + cmd_data.cmd_id = (__u32)buffer[4]; + cmd_data.data_len = (__u32)buffer[8]; + memcpy(cmd_data.data, &buffer[12], cmd_data.data_len); + cmd_data.next = NULL; + + /* Send through callback fn */ + callback = hdmi_service_callback_get(); + LOGHDMILIB("callback:%p", callback); + if (callback) + callback(cmd_data.cmd, cmd_data.data_len, + cmd_data.data); + } + } + +thread_sockserver_fn_end: + close(sock); + + LOGHDMILIB("%s end res:%d", __func__, res); + pthread_exit(NULL); +} +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + +int serversocket_create(int avoid_return_msg) +{ + int sock; + int len; + struct sockaddr_un addr; + int result; + int n; + + LOGHDMILIB("%s begin", __func__); + + no_return_msg = avoid_return_msg; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + LOGHDMILIB("sock:%d", sock); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, SOCKET_LISTEN_PATH); + len = sizeof(addr); + + n = connect(sock, (struct sockaddr *)&addr, len); + LOGHDMILIB("connect:%d", n); + + if (n < 0) { + LOGHDMILIB("socket connect err:%d", n); + goto socket_test_end; + } + +socket_test_end: + LOGHDMILIB("%s end", __func__); + if (sock >= 0) + serversocket_set(sock); + LOGHDMILIB("servsock:%d", sock); + +#ifdef HDMI_SERVICE_USE_CALLBACK_FN + /* Create a server thread */ + pthread_create(&thread_sockserver, NULL, + (void *)thread_sockserver_fn, (void *)sock); +#endif /*HDMI_SERVICE_USE_CALLBACK_FN*/ + return sock; +} + +static int serversocket_get(void) +{ + return serversocket; +} + +int serversocket_write(int len, __u8 *data) +{ + int n; + int sock; + + sock = serversocket_get(); + n = write(sock, data, len); + LOGHDMILIB("write socket len res:%d %d %d", sock, len, n); + + return n; +} + +int serversocket_close(void) +{ + int sock; + + sock = serversocket_get(); + return close(sock); +} -- cgit v1.2.3