1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * FireDTV driver (formerly known as FireSAT) |
4 | * |
5 | * Copyright (C) 2004 Andreas Monitzer <andy@monitzer.com> |
6 | * Copyright (C) 2008 Henrik Kurelid <henrik@kurelid.se> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/dvb/ca.h> |
11 | #include <linux/fs.h> |
12 | #include <linux/module.h> |
13 | |
14 | #include <media/dvbdev.h> |
15 | |
16 | #include "firedtv.h" |
17 | |
18 | #define EN50221_TAG_APP_INFO_ENQUIRY 0x9f8020 |
19 | #define EN50221_TAG_CA_INFO_ENQUIRY 0x9f8030 |
20 | #define EN50221_TAG_CA_PMT 0x9f8032 |
21 | #define 0x9f8022 |
22 | |
23 | static int fdtv_ca_ready(struct firedtv_tuner_status *stat) |
24 | { |
25 | return stat->ca_initialization_status == 1 && |
26 | stat->ca_error_flag == 0 && |
27 | stat->ca_dvb_flag == 1 && |
28 | stat->ca_module_present_status == 1; |
29 | } |
30 | |
31 | static int fdtv_get_ca_flags(struct firedtv_tuner_status *stat) |
32 | { |
33 | int flags = 0; |
34 | |
35 | if (stat->ca_module_present_status == 1) |
36 | flags |= CA_CI_MODULE_PRESENT; |
37 | if (stat->ca_initialization_status == 1 && |
38 | stat->ca_error_flag == 0 && |
39 | stat->ca_dvb_flag == 1) |
40 | flags |= CA_CI_MODULE_READY; |
41 | return flags; |
42 | } |
43 | |
44 | static int fdtv_ca_get_caps(void *arg) |
45 | { |
46 | struct ca_caps *cap = arg; |
47 | |
48 | cap->slot_num = 1; |
49 | cap->slot_type = CA_CI; |
50 | cap->descr_num = 1; |
51 | cap->descr_type = CA_ECD; |
52 | return 0; |
53 | } |
54 | |
55 | static int fdtv_ca_get_slot_info(struct firedtv *fdtv, void *arg) |
56 | { |
57 | struct firedtv_tuner_status stat; |
58 | struct ca_slot_info *slot = arg; |
59 | int err; |
60 | |
61 | err = avc_tuner_status(fdtv, stat: &stat); |
62 | if (err) |
63 | return err; |
64 | |
65 | if (slot->num != 0) |
66 | return -EACCES; |
67 | |
68 | slot->type = CA_CI; |
69 | slot->flags = fdtv_get_ca_flags(stat: &stat); |
70 | return 0; |
71 | } |
72 | |
73 | static int fdtv_ca_app_info(struct firedtv *fdtv, void *arg) |
74 | { |
75 | struct ca_msg *reply = arg; |
76 | |
77 | return avc_ca_app_info(fdtv, app_info: reply->msg, len: &reply->length); |
78 | } |
79 | |
80 | static int fdtv_ca_info(struct firedtv *fdtv, void *arg) |
81 | { |
82 | struct ca_msg *reply = arg; |
83 | |
84 | return avc_ca_info(fdtv, app_info: reply->msg, len: &reply->length); |
85 | } |
86 | |
87 | static int fdtv_ca_get_mmi(struct firedtv *fdtv, void *arg) |
88 | { |
89 | struct ca_msg *reply = arg; |
90 | |
91 | return avc_ca_get_mmi(fdtv, mmi_object: reply->msg, len: &reply->length); |
92 | } |
93 | |
94 | static int fdtv_ca_get_msg(struct firedtv *fdtv, void *arg) |
95 | { |
96 | struct firedtv_tuner_status stat; |
97 | int err; |
98 | |
99 | switch (fdtv->ca_last_command) { |
100 | case EN50221_TAG_APP_INFO_ENQUIRY: |
101 | err = fdtv_ca_app_info(fdtv, arg); |
102 | break; |
103 | case EN50221_TAG_CA_INFO_ENQUIRY: |
104 | err = fdtv_ca_info(fdtv, arg); |
105 | break; |
106 | default: |
107 | err = avc_tuner_status(fdtv, stat: &stat); |
108 | if (err) |
109 | break; |
110 | if (stat.ca_mmi == 1) |
111 | err = fdtv_ca_get_mmi(fdtv, arg); |
112 | else { |
113 | dev_info(fdtv->device, "unhandled CA message 0x%08x\n" , |
114 | fdtv->ca_last_command); |
115 | err = -EACCES; |
116 | } |
117 | } |
118 | fdtv->ca_last_command = 0; |
119 | return err; |
120 | } |
121 | |
122 | static int fdtv_ca_pmt(struct firedtv *fdtv, void *arg) |
123 | { |
124 | struct ca_msg *msg = arg; |
125 | int data_pos; |
126 | int data_length; |
127 | int i; |
128 | |
129 | data_pos = 4; |
130 | if (msg->msg[3] & 0x80) { |
131 | data_length = 0; |
132 | for (i = 0; i < (msg->msg[3] & 0x7f); i++) |
133 | data_length = (data_length << 8) + msg->msg[data_pos++]; |
134 | } else { |
135 | data_length = msg->msg[3]; |
136 | } |
137 | if (data_length > sizeof(msg->msg) - data_pos) |
138 | return -EINVAL; |
139 | |
140 | return avc_ca_pmt(fdtv, app_info: &msg->msg[data_pos], length: data_length); |
141 | } |
142 | |
143 | static int fdtv_ca_send_msg(struct firedtv *fdtv, void *arg) |
144 | { |
145 | struct ca_msg *msg = arg; |
146 | int err; |
147 | |
148 | /* Do we need a semaphore for this? */ |
149 | fdtv->ca_last_command = |
150 | (msg->msg[0] << 16) + (msg->msg[1] << 8) + msg->msg[2]; |
151 | switch (fdtv->ca_last_command) { |
152 | case EN50221_TAG_CA_PMT: |
153 | err = fdtv_ca_pmt(fdtv, arg); |
154 | break; |
155 | case EN50221_TAG_APP_INFO_ENQUIRY: |
156 | /* handled in ca_get_msg */ |
157 | err = 0; |
158 | break; |
159 | case EN50221_TAG_CA_INFO_ENQUIRY: |
160 | /* handled in ca_get_msg */ |
161 | err = 0; |
162 | break; |
163 | case EN50221_TAG_ENTER_MENU: |
164 | err = avc_ca_enter_menu(fdtv); |
165 | break; |
166 | default: |
167 | dev_err(fdtv->device, "unhandled CA message 0x%08x\n" , |
168 | fdtv->ca_last_command); |
169 | err = -EACCES; |
170 | } |
171 | return err; |
172 | } |
173 | |
174 | static int fdtv_ca_ioctl(struct file *file, unsigned int cmd, void *arg) |
175 | { |
176 | struct dvb_device *dvbdev = file->private_data; |
177 | struct firedtv *fdtv = dvbdev->priv; |
178 | struct firedtv_tuner_status stat; |
179 | int err; |
180 | |
181 | switch (cmd) { |
182 | case CA_RESET: |
183 | err = avc_ca_reset(fdtv); |
184 | break; |
185 | case CA_GET_CAP: |
186 | err = fdtv_ca_get_caps(arg); |
187 | break; |
188 | case CA_GET_SLOT_INFO: |
189 | err = fdtv_ca_get_slot_info(fdtv, arg); |
190 | break; |
191 | case CA_GET_MSG: |
192 | err = fdtv_ca_get_msg(fdtv, arg); |
193 | break; |
194 | case CA_SEND_MSG: |
195 | err = fdtv_ca_send_msg(fdtv, arg); |
196 | break; |
197 | default: |
198 | dev_info(fdtv->device, "unhandled CA ioctl %u\n" , cmd); |
199 | err = -EOPNOTSUPP; |
200 | } |
201 | |
202 | /* FIXME Is this necessary? */ |
203 | avc_tuner_status(fdtv, stat: &stat); |
204 | |
205 | return err; |
206 | } |
207 | |
208 | static __poll_t fdtv_ca_io_poll(struct file *file, poll_table *wait) |
209 | { |
210 | return EPOLLIN; |
211 | } |
212 | |
213 | static const struct file_operations fdtv_ca_fops = { |
214 | .owner = THIS_MODULE, |
215 | .unlocked_ioctl = dvb_generic_ioctl, |
216 | .open = dvb_generic_open, |
217 | .release = dvb_generic_release, |
218 | .poll = fdtv_ca_io_poll, |
219 | .llseek = noop_llseek, |
220 | }; |
221 | |
222 | static const struct dvb_device fdtv_ca = { |
223 | .users = 1, |
224 | .readers = 1, |
225 | .writers = 1, |
226 | .fops = &fdtv_ca_fops, |
227 | .kernel_ioctl = fdtv_ca_ioctl, |
228 | }; |
229 | |
230 | int fdtv_ca_register(struct firedtv *fdtv) |
231 | { |
232 | struct firedtv_tuner_status stat; |
233 | int err; |
234 | |
235 | if (avc_tuner_status(fdtv, stat: &stat)) |
236 | return -EINVAL; |
237 | |
238 | if (!fdtv_ca_ready(stat: &stat)) |
239 | return -EFAULT; |
240 | |
241 | err = dvb_register_device(adap: &fdtv->adapter, pdvbdev: &fdtv->cadev, |
242 | template: &fdtv_ca, priv: fdtv, type: DVB_DEVICE_CA, demux_sink_pads: 0); |
243 | |
244 | if (stat.ca_application_info == 0) |
245 | dev_err(fdtv->device, "CaApplicationInfo is not set\n" ); |
246 | if (stat.ca_date_time_request == 1) |
247 | avc_ca_get_time_date(fdtv, interval: &fdtv->ca_time_interval); |
248 | |
249 | return err; |
250 | } |
251 | |
252 | void fdtv_ca_release(struct firedtv *fdtv) |
253 | { |
254 | dvb_unregister_device(dvbdev: fdtv->cadev); |
255 | } |
256 | |