18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * FireDTV driver (formerly known as FireSAT) 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2004 Andreas Monitzer <andy@monitzer.com> 68c2ecf20Sopenharmony_ci * Copyright (C) 2008 Henrik Kurelid <henrik@kurelid.se> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/device.h> 108c2ecf20Sopenharmony_ci#include <linux/dvb/ca.h> 118c2ecf20Sopenharmony_ci#include <linux/fs.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <media/dvbdev.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include "firedtv.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define EN50221_TAG_APP_INFO_ENQUIRY 0x9f8020 198c2ecf20Sopenharmony_ci#define EN50221_TAG_CA_INFO_ENQUIRY 0x9f8030 208c2ecf20Sopenharmony_ci#define EN50221_TAG_CA_PMT 0x9f8032 218c2ecf20Sopenharmony_ci#define EN50221_TAG_ENTER_MENU 0x9f8022 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic int fdtv_ca_ready(struct firedtv_tuner_status *stat) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci return stat->ca_initialization_status == 1 && 268c2ecf20Sopenharmony_ci stat->ca_error_flag == 0 && 278c2ecf20Sopenharmony_ci stat->ca_dvb_flag == 1 && 288c2ecf20Sopenharmony_ci stat->ca_module_present_status == 1; 298c2ecf20Sopenharmony_ci} 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int fdtv_get_ca_flags(struct firedtv_tuner_status *stat) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci int flags = 0; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci if (stat->ca_module_present_status == 1) 368c2ecf20Sopenharmony_ci flags |= CA_CI_MODULE_PRESENT; 378c2ecf20Sopenharmony_ci if (stat->ca_initialization_status == 1 && 388c2ecf20Sopenharmony_ci stat->ca_error_flag == 0 && 398c2ecf20Sopenharmony_ci stat->ca_dvb_flag == 1) 408c2ecf20Sopenharmony_ci flags |= CA_CI_MODULE_READY; 418c2ecf20Sopenharmony_ci return flags; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic int fdtv_ca_get_caps(void *arg) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci struct ca_caps *cap = arg; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci cap->slot_num = 1; 498c2ecf20Sopenharmony_ci cap->slot_type = CA_CI; 508c2ecf20Sopenharmony_ci cap->descr_num = 1; 518c2ecf20Sopenharmony_ci cap->descr_type = CA_ECD; 528c2ecf20Sopenharmony_ci return 0; 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic int fdtv_ca_get_slot_info(struct firedtv *fdtv, void *arg) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct firedtv_tuner_status stat; 588c2ecf20Sopenharmony_ci struct ca_slot_info *slot = arg; 598c2ecf20Sopenharmony_ci int err; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci err = avc_tuner_status(fdtv, &stat); 628c2ecf20Sopenharmony_ci if (err) 638c2ecf20Sopenharmony_ci return err; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (slot->num != 0) 668c2ecf20Sopenharmony_ci return -EACCES; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci slot->type = CA_CI; 698c2ecf20Sopenharmony_ci slot->flags = fdtv_get_ca_flags(&stat); 708c2ecf20Sopenharmony_ci return 0; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int fdtv_ca_app_info(struct firedtv *fdtv, void *arg) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct ca_msg *reply = arg; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return avc_ca_app_info(fdtv, reply->msg, &reply->length); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int fdtv_ca_info(struct firedtv *fdtv, void *arg) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct ca_msg *reply = arg; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci return avc_ca_info(fdtv, reply->msg, &reply->length); 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int fdtv_ca_get_mmi(struct firedtv *fdtv, void *arg) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct ca_msg *reply = arg; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci return avc_ca_get_mmi(fdtv, reply->msg, &reply->length); 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int fdtv_ca_get_msg(struct firedtv *fdtv, void *arg) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct firedtv_tuner_status stat; 978c2ecf20Sopenharmony_ci int err; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci switch (fdtv->ca_last_command) { 1008c2ecf20Sopenharmony_ci case EN50221_TAG_APP_INFO_ENQUIRY: 1018c2ecf20Sopenharmony_ci err = fdtv_ca_app_info(fdtv, arg); 1028c2ecf20Sopenharmony_ci break; 1038c2ecf20Sopenharmony_ci case EN50221_TAG_CA_INFO_ENQUIRY: 1048c2ecf20Sopenharmony_ci err = fdtv_ca_info(fdtv, arg); 1058c2ecf20Sopenharmony_ci break; 1068c2ecf20Sopenharmony_ci default: 1078c2ecf20Sopenharmony_ci err = avc_tuner_status(fdtv, &stat); 1088c2ecf20Sopenharmony_ci if (err) 1098c2ecf20Sopenharmony_ci break; 1108c2ecf20Sopenharmony_ci if (stat.ca_mmi == 1) 1118c2ecf20Sopenharmony_ci err = fdtv_ca_get_mmi(fdtv, arg); 1128c2ecf20Sopenharmony_ci else { 1138c2ecf20Sopenharmony_ci dev_info(fdtv->device, "unhandled CA message 0x%08x\n", 1148c2ecf20Sopenharmony_ci fdtv->ca_last_command); 1158c2ecf20Sopenharmony_ci err = -EACCES; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci fdtv->ca_last_command = 0; 1198c2ecf20Sopenharmony_ci return err; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int fdtv_ca_pmt(struct firedtv *fdtv, void *arg) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci struct ca_msg *msg = arg; 1258c2ecf20Sopenharmony_ci int data_pos; 1268c2ecf20Sopenharmony_ci int data_length; 1278c2ecf20Sopenharmony_ci int i; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci data_pos = 4; 1308c2ecf20Sopenharmony_ci if (msg->msg[3] & 0x80) { 1318c2ecf20Sopenharmony_ci data_length = 0; 1328c2ecf20Sopenharmony_ci for (i = 0; i < (msg->msg[3] & 0x7f); i++) 1338c2ecf20Sopenharmony_ci data_length = (data_length << 8) + msg->msg[data_pos++]; 1348c2ecf20Sopenharmony_ci } else { 1358c2ecf20Sopenharmony_ci data_length = msg->msg[3]; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci if (data_length > sizeof(msg->msg) - data_pos) 1388c2ecf20Sopenharmony_ci return -EINVAL; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return avc_ca_pmt(fdtv, &msg->msg[data_pos], data_length); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int fdtv_ca_send_msg(struct firedtv *fdtv, void *arg) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct ca_msg *msg = arg; 1468c2ecf20Sopenharmony_ci int err; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* Do we need a semaphore for this? */ 1498c2ecf20Sopenharmony_ci fdtv->ca_last_command = 1508c2ecf20Sopenharmony_ci (msg->msg[0] << 16) + (msg->msg[1] << 8) + msg->msg[2]; 1518c2ecf20Sopenharmony_ci switch (fdtv->ca_last_command) { 1528c2ecf20Sopenharmony_ci case EN50221_TAG_CA_PMT: 1538c2ecf20Sopenharmony_ci err = fdtv_ca_pmt(fdtv, arg); 1548c2ecf20Sopenharmony_ci break; 1558c2ecf20Sopenharmony_ci case EN50221_TAG_APP_INFO_ENQUIRY: 1568c2ecf20Sopenharmony_ci /* handled in ca_get_msg */ 1578c2ecf20Sopenharmony_ci err = 0; 1588c2ecf20Sopenharmony_ci break; 1598c2ecf20Sopenharmony_ci case EN50221_TAG_CA_INFO_ENQUIRY: 1608c2ecf20Sopenharmony_ci /* handled in ca_get_msg */ 1618c2ecf20Sopenharmony_ci err = 0; 1628c2ecf20Sopenharmony_ci break; 1638c2ecf20Sopenharmony_ci case EN50221_TAG_ENTER_MENU: 1648c2ecf20Sopenharmony_ci err = avc_ca_enter_menu(fdtv); 1658c2ecf20Sopenharmony_ci break; 1668c2ecf20Sopenharmony_ci default: 1678c2ecf20Sopenharmony_ci dev_err(fdtv->device, "unhandled CA message 0x%08x\n", 1688c2ecf20Sopenharmony_ci fdtv->ca_last_command); 1698c2ecf20Sopenharmony_ci err = -EACCES; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci return err; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic int fdtv_ca_ioctl(struct file *file, unsigned int cmd, void *arg) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci struct dvb_device *dvbdev = file->private_data; 1778c2ecf20Sopenharmony_ci struct firedtv *fdtv = dvbdev->priv; 1788c2ecf20Sopenharmony_ci struct firedtv_tuner_status stat; 1798c2ecf20Sopenharmony_ci int err; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci switch (cmd) { 1828c2ecf20Sopenharmony_ci case CA_RESET: 1838c2ecf20Sopenharmony_ci err = avc_ca_reset(fdtv); 1848c2ecf20Sopenharmony_ci break; 1858c2ecf20Sopenharmony_ci case CA_GET_CAP: 1868c2ecf20Sopenharmony_ci err = fdtv_ca_get_caps(arg); 1878c2ecf20Sopenharmony_ci break; 1888c2ecf20Sopenharmony_ci case CA_GET_SLOT_INFO: 1898c2ecf20Sopenharmony_ci err = fdtv_ca_get_slot_info(fdtv, arg); 1908c2ecf20Sopenharmony_ci break; 1918c2ecf20Sopenharmony_ci case CA_GET_MSG: 1928c2ecf20Sopenharmony_ci err = fdtv_ca_get_msg(fdtv, arg); 1938c2ecf20Sopenharmony_ci break; 1948c2ecf20Sopenharmony_ci case CA_SEND_MSG: 1958c2ecf20Sopenharmony_ci err = fdtv_ca_send_msg(fdtv, arg); 1968c2ecf20Sopenharmony_ci break; 1978c2ecf20Sopenharmony_ci default: 1988c2ecf20Sopenharmony_ci dev_info(fdtv->device, "unhandled CA ioctl %u\n", cmd); 1998c2ecf20Sopenharmony_ci err = -EOPNOTSUPP; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* FIXME Is this necessary? */ 2038c2ecf20Sopenharmony_ci avc_tuner_status(fdtv, &stat); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci return err; 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic __poll_t fdtv_ca_io_poll(struct file *file, poll_table *wait) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci return EPOLLIN; 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic const struct file_operations fdtv_ca_fops = { 2148c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2158c2ecf20Sopenharmony_ci .unlocked_ioctl = dvb_generic_ioctl, 2168c2ecf20Sopenharmony_ci .open = dvb_generic_open, 2178c2ecf20Sopenharmony_ci .release = dvb_generic_release, 2188c2ecf20Sopenharmony_ci .poll = fdtv_ca_io_poll, 2198c2ecf20Sopenharmony_ci .llseek = noop_llseek, 2208c2ecf20Sopenharmony_ci}; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic const struct dvb_device fdtv_ca = { 2238c2ecf20Sopenharmony_ci .users = 1, 2248c2ecf20Sopenharmony_ci .readers = 1, 2258c2ecf20Sopenharmony_ci .writers = 1, 2268c2ecf20Sopenharmony_ci .fops = &fdtv_ca_fops, 2278c2ecf20Sopenharmony_ci .kernel_ioctl = fdtv_ca_ioctl, 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ciint fdtv_ca_register(struct firedtv *fdtv) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct firedtv_tuner_status stat; 2338c2ecf20Sopenharmony_ci int err; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci if (avc_tuner_status(fdtv, &stat)) 2368c2ecf20Sopenharmony_ci return -EINVAL; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci if (!fdtv_ca_ready(&stat)) 2398c2ecf20Sopenharmony_ci return -EFAULT; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci err = dvb_register_device(&fdtv->adapter, &fdtv->cadev, 2428c2ecf20Sopenharmony_ci &fdtv_ca, fdtv, DVB_DEVICE_CA, 0); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (stat.ca_application_info == 0) 2458c2ecf20Sopenharmony_ci dev_err(fdtv->device, "CaApplicationInfo is not set\n"); 2468c2ecf20Sopenharmony_ci if (stat.ca_date_time_request == 1) 2478c2ecf20Sopenharmony_ci avc_ca_get_time_date(fdtv, &fdtv->ca_time_interval); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci return err; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_civoid fdtv_ca_release(struct firedtv *fdtv) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci dvb_unregister_device(fdtv->cadev); 2558c2ecf20Sopenharmony_ci} 256