mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
9.4 KiB
408 lines
9.4 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* |
|
* Copyright (C) 2005 Mike Isely <[email protected]> |
|
*/ |
|
|
|
#include "pvrusb2-context.h" |
|
#include "pvrusb2-io.h" |
|
#include "pvrusb2-ioread.h" |
|
#include "pvrusb2-hdw.h" |
|
#include "pvrusb2-debug.h" |
|
#include <linux/wait.h> |
|
#include <linux/kthread.h> |
|
#include <linux/errno.h> |
|
#include <linux/string.h> |
|
#include <linux/slab.h> |
|
|
|
static struct pvr2_context *pvr2_context_exist_first; |
|
static struct pvr2_context *pvr2_context_exist_last; |
|
static struct pvr2_context *pvr2_context_notify_first; |
|
static struct pvr2_context *pvr2_context_notify_last; |
|
static DEFINE_MUTEX(pvr2_context_mutex); |
|
static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data); |
|
static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_cleanup_data); |
|
static int pvr2_context_cleanup_flag; |
|
static int pvr2_context_cleaned_flag; |
|
static struct task_struct *pvr2_context_thread_ptr; |
|
|
|
|
|
static void pvr2_context_set_notify(struct pvr2_context *mp, int fl) |
|
{ |
|
int signal_flag = 0; |
|
mutex_lock(&pvr2_context_mutex); |
|
if (fl) { |
|
if (!mp->notify_flag) { |
|
signal_flag = (pvr2_context_notify_first == NULL); |
|
mp->notify_prev = pvr2_context_notify_last; |
|
mp->notify_next = NULL; |
|
pvr2_context_notify_last = mp; |
|
if (mp->notify_prev) { |
|
mp->notify_prev->notify_next = mp; |
|
} else { |
|
pvr2_context_notify_first = mp; |
|
} |
|
mp->notify_flag = !0; |
|
} |
|
} else { |
|
if (mp->notify_flag) { |
|
mp->notify_flag = 0; |
|
if (mp->notify_next) { |
|
mp->notify_next->notify_prev = mp->notify_prev; |
|
} else { |
|
pvr2_context_notify_last = mp->notify_prev; |
|
} |
|
if (mp->notify_prev) { |
|
mp->notify_prev->notify_next = mp->notify_next; |
|
} else { |
|
pvr2_context_notify_first = mp->notify_next; |
|
} |
|
} |
|
} |
|
mutex_unlock(&pvr2_context_mutex); |
|
if (signal_flag) wake_up(&pvr2_context_sync_data); |
|
} |
|
|
|
|
|
static void pvr2_context_destroy(struct pvr2_context *mp) |
|
{ |
|
pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (destroy)",mp); |
|
pvr2_hdw_destroy(mp->hdw); |
|
pvr2_context_set_notify(mp, 0); |
|
mutex_lock(&pvr2_context_mutex); |
|
if (mp->exist_next) { |
|
mp->exist_next->exist_prev = mp->exist_prev; |
|
} else { |
|
pvr2_context_exist_last = mp->exist_prev; |
|
} |
|
if (mp->exist_prev) { |
|
mp->exist_prev->exist_next = mp->exist_next; |
|
} else { |
|
pvr2_context_exist_first = mp->exist_next; |
|
} |
|
if (!pvr2_context_exist_first) { |
|
/* Trigger wakeup on control thread in case it is waiting |
|
for an exit condition. */ |
|
wake_up(&pvr2_context_sync_data); |
|
} |
|
mutex_unlock(&pvr2_context_mutex); |
|
kfree(mp); |
|
} |
|
|
|
|
|
static void pvr2_context_notify(struct pvr2_context *mp) |
|
{ |
|
pvr2_context_set_notify(mp,!0); |
|
} |
|
|
|
|
|
static void pvr2_context_check(struct pvr2_context *mp) |
|
{ |
|
struct pvr2_channel *ch1, *ch2; |
|
pvr2_trace(PVR2_TRACE_CTXT, |
|
"pvr2_context %p (notify)", mp); |
|
if (!mp->initialized_flag && !mp->disconnect_flag) { |
|
mp->initialized_flag = !0; |
|
pvr2_trace(PVR2_TRACE_CTXT, |
|
"pvr2_context %p (initialize)", mp); |
|
/* Finish hardware initialization */ |
|
if (pvr2_hdw_initialize(mp->hdw, |
|
(void (*)(void *))pvr2_context_notify, |
|
mp)) { |
|
mp->video_stream.stream = |
|
pvr2_hdw_get_video_stream(mp->hdw); |
|
/* Trigger interface initialization. By doing this |
|
here initialization runs in our own safe and |
|
cozy thread context. */ |
|
if (mp->setup_func) mp->setup_func(mp); |
|
} else { |
|
pvr2_trace(PVR2_TRACE_CTXT, |
|
"pvr2_context %p (thread skipping setup)", |
|
mp); |
|
/* Even though initialization did not succeed, |
|
we're still going to continue anyway. We need |
|
to do this in order to await the expected |
|
disconnect (which we will detect in the normal |
|
course of operation). */ |
|
} |
|
} |
|
|
|
for (ch1 = mp->mc_first; ch1; ch1 = ch2) { |
|
ch2 = ch1->mc_next; |
|
if (ch1->check_func) ch1->check_func(ch1); |
|
} |
|
|
|
if (mp->disconnect_flag && !mp->mc_first) { |
|
/* Go away... */ |
|
pvr2_context_destroy(mp); |
|
return; |
|
} |
|
} |
|
|
|
|
|
static int pvr2_context_shutok(void) |
|
{ |
|
return pvr2_context_cleanup_flag && (pvr2_context_exist_first == NULL); |
|
} |
|
|
|
|
|
static int pvr2_context_thread_func(void *foo) |
|
{ |
|
struct pvr2_context *mp; |
|
|
|
pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread start"); |
|
|
|
do { |
|
while ((mp = pvr2_context_notify_first) != NULL) { |
|
pvr2_context_set_notify(mp, 0); |
|
pvr2_context_check(mp); |
|
} |
|
wait_event_interruptible( |
|
pvr2_context_sync_data, |
|
((pvr2_context_notify_first != NULL) || |
|
pvr2_context_shutok())); |
|
} while (!pvr2_context_shutok()); |
|
|
|
pvr2_context_cleaned_flag = !0; |
|
wake_up(&pvr2_context_cleanup_data); |
|
|
|
pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread cleaned up"); |
|
|
|
wait_event_interruptible( |
|
pvr2_context_sync_data, |
|
kthread_should_stop()); |
|
|
|
pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end"); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int pvr2_context_global_init(void) |
|
{ |
|
pvr2_context_thread_ptr = kthread_run(pvr2_context_thread_func, |
|
NULL, |
|
"pvrusb2-context"); |
|
return IS_ERR(pvr2_context_thread_ptr) ? -ENOMEM : 0; |
|
} |
|
|
|
|
|
void pvr2_context_global_done(void) |
|
{ |
|
pvr2_context_cleanup_flag = !0; |
|
wake_up(&pvr2_context_sync_data); |
|
wait_event_interruptible( |
|
pvr2_context_cleanup_data, |
|
pvr2_context_cleaned_flag); |
|
kthread_stop(pvr2_context_thread_ptr); |
|
} |
|
|
|
|
|
struct pvr2_context *pvr2_context_create( |
|
struct usb_interface *intf, |
|
const struct usb_device_id *devid, |
|
void (*setup_func)(struct pvr2_context *)) |
|
{ |
|
struct pvr2_context *mp = NULL; |
|
mp = kzalloc(sizeof(*mp),GFP_KERNEL); |
|
if (!mp) goto done; |
|
pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (create)",mp); |
|
mp->setup_func = setup_func; |
|
mutex_init(&mp->mutex); |
|
mutex_lock(&pvr2_context_mutex); |
|
mp->exist_prev = pvr2_context_exist_last; |
|
mp->exist_next = NULL; |
|
pvr2_context_exist_last = mp; |
|
if (mp->exist_prev) { |
|
mp->exist_prev->exist_next = mp; |
|
} else { |
|
pvr2_context_exist_first = mp; |
|
} |
|
mutex_unlock(&pvr2_context_mutex); |
|
mp->hdw = pvr2_hdw_create(intf,devid); |
|
if (!mp->hdw) { |
|
pvr2_context_destroy(mp); |
|
mp = NULL; |
|
goto done; |
|
} |
|
pvr2_context_set_notify(mp, !0); |
|
done: |
|
return mp; |
|
} |
|
|
|
|
|
static void pvr2_context_reset_input_limits(struct pvr2_context *mp) |
|
{ |
|
unsigned int tmsk,mmsk; |
|
struct pvr2_channel *cp; |
|
struct pvr2_hdw *hdw = mp->hdw; |
|
mmsk = pvr2_hdw_get_input_available(hdw); |
|
tmsk = mmsk; |
|
for (cp = mp->mc_first; cp; cp = cp->mc_next) { |
|
if (!cp->input_mask) continue; |
|
tmsk &= cp->input_mask; |
|
} |
|
pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk); |
|
pvr2_hdw_commit_ctl(hdw); |
|
} |
|
|
|
|
|
static void pvr2_context_enter(struct pvr2_context *mp) |
|
{ |
|
mutex_lock(&mp->mutex); |
|
} |
|
|
|
|
|
static void pvr2_context_exit(struct pvr2_context *mp) |
|
{ |
|
int destroy_flag = 0; |
|
if (!(mp->mc_first || !mp->disconnect_flag)) { |
|
destroy_flag = !0; |
|
} |
|
mutex_unlock(&mp->mutex); |
|
if (destroy_flag) pvr2_context_notify(mp); |
|
} |
|
|
|
|
|
void pvr2_context_disconnect(struct pvr2_context *mp) |
|
{ |
|
pvr2_hdw_disconnect(mp->hdw); |
|
mp->disconnect_flag = !0; |
|
pvr2_context_notify(mp); |
|
} |
|
|
|
|
|
void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp) |
|
{ |
|
pvr2_context_enter(mp); |
|
cp->hdw = mp->hdw; |
|
cp->mc_head = mp; |
|
cp->mc_next = NULL; |
|
cp->mc_prev = mp->mc_last; |
|
if (mp->mc_last) { |
|
mp->mc_last->mc_next = cp; |
|
} else { |
|
mp->mc_first = cp; |
|
} |
|
mp->mc_last = cp; |
|
pvr2_context_exit(mp); |
|
} |
|
|
|
|
|
static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp) |
|
{ |
|
if (!cp->stream) return; |
|
pvr2_stream_kill(cp->stream->stream); |
|
cp->stream->user = NULL; |
|
cp->stream = NULL; |
|
} |
|
|
|
|
|
void pvr2_channel_done(struct pvr2_channel *cp) |
|
{ |
|
struct pvr2_context *mp = cp->mc_head; |
|
pvr2_context_enter(mp); |
|
cp->input_mask = 0; |
|
pvr2_channel_disclaim_stream(cp); |
|
pvr2_context_reset_input_limits(mp); |
|
if (cp->mc_next) { |
|
cp->mc_next->mc_prev = cp->mc_prev; |
|
} else { |
|
mp->mc_last = cp->mc_prev; |
|
} |
|
if (cp->mc_prev) { |
|
cp->mc_prev->mc_next = cp->mc_next; |
|
} else { |
|
mp->mc_first = cp->mc_next; |
|
} |
|
cp->hdw = NULL; |
|
pvr2_context_exit(mp); |
|
} |
|
|
|
|
|
int pvr2_channel_limit_inputs(struct pvr2_channel *cp,unsigned int cmsk) |
|
{ |
|
unsigned int tmsk,mmsk; |
|
int ret = 0; |
|
struct pvr2_channel *p2; |
|
struct pvr2_hdw *hdw = cp->hdw; |
|
|
|
mmsk = pvr2_hdw_get_input_available(hdw); |
|
cmsk &= mmsk; |
|
if (cmsk == cp->input_mask) { |
|
/* No change; nothing to do */ |
|
return 0; |
|
} |
|
|
|
pvr2_context_enter(cp->mc_head); |
|
do { |
|
if (!cmsk) { |
|
cp->input_mask = 0; |
|
pvr2_context_reset_input_limits(cp->mc_head); |
|
break; |
|
} |
|
tmsk = mmsk; |
|
for (p2 = cp->mc_head->mc_first; p2; p2 = p2->mc_next) { |
|
if (p2 == cp) continue; |
|
if (!p2->input_mask) continue; |
|
tmsk &= p2->input_mask; |
|
} |
|
if (!(tmsk & cmsk)) { |
|
ret = -EPERM; |
|
break; |
|
} |
|
tmsk &= cmsk; |
|
if ((ret = pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk)) != 0) { |
|
/* Internal failure changing allowed list; probably |
|
should not happen, but react if it does. */ |
|
break; |
|
} |
|
cp->input_mask = cmsk; |
|
pvr2_hdw_commit_ctl(hdw); |
|
} while (0); |
|
pvr2_context_exit(cp->mc_head); |
|
return ret; |
|
} |
|
|
|
|
|
unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *cp) |
|
{ |
|
return cp->input_mask; |
|
} |
|
|
|
|
|
int pvr2_channel_claim_stream(struct pvr2_channel *cp, |
|
struct pvr2_context_stream *sp) |
|
{ |
|
int code = 0; |
|
pvr2_context_enter(cp->mc_head); do { |
|
if (sp == cp->stream) break; |
|
if (sp && sp->user) { |
|
code = -EBUSY; |
|
break; |
|
} |
|
pvr2_channel_disclaim_stream(cp); |
|
if (!sp) break; |
|
sp->user = cp; |
|
cp->stream = sp; |
|
} while (0); |
|
pvr2_context_exit(cp->mc_head); |
|
return code; |
|
} |
|
|
|
|
|
// This is the marker for the real beginning of a legitimate mpeg2 stream. |
|
static char stream_sync_key[] = { |
|
0x00, 0x00, 0x01, 0xba, |
|
}; |
|
|
|
struct pvr2_ioread *pvr2_channel_create_mpeg_stream( |
|
struct pvr2_context_stream *sp) |
|
{ |
|
struct pvr2_ioread *cp; |
|
cp = pvr2_ioread_create(); |
|
if (!cp) return NULL; |
|
pvr2_ioread_setup(cp,sp->stream); |
|
pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key)); |
|
return cp; |
|
}
|
|
|