public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Michael Hunold <hunold@linuxtv.org>
To: hunold@linuxtv.org, torvalds@osdl.org, akpm@osdl.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH 5/9] DVB: Other DVB core updates
Date: Mon, 26 Apr 2004 09:42:11 -0400	[thread overview]
Message-ID: <10829868582688@convergence.de> (raw)
In-Reply-To: <1082986832779@convergence.de>

- [DVB] remove superflous memset() which caused section data to be overwritten when a) there are two sections in one TS packet, and b) the first section was smaller than 18 bytes; thanks to Jean-Claude Repetto for tracking this down
- [DVB] starting a ts filter on a running section filter's pid did break the section filter; fixed.
- [DVB] integrate ULE Decapsulation code, thanks to gcs - Global Communication & Services GmbH. and Institute for Computer Sciences Salzburg University. Hilmar Linder <hlinder@cosy.sbg.ac.at> and Wolfram Stering <wstering@cosy.sbg.ac.at>
- [DVB] fix the module use count bugs, thanks to Hernan A.Perez Masci for his initial work on this problem
- [DVB] if dvb_frontend_internal_ioctl() returns an error code, be sure to deliver it to the calling application, don't ignore it (fixes the bug that the frontend0 doesn't respond properly to unknown ioctls...)
- [DVB] major frontend code clean up, rewritten core tuning loop. Thanks to Andrew de Quincey.
- [DVB] follow changes in dvb-core in skystar2, dvb-bt8xx
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/b2c2/skystar2.c linux-2.6.5-patched/drivers/media/dvb/b2c2/skystar2.c
--- xx-linux-2.6.5/drivers/media/dvb/b2c2/skystar2.c	2004-03-12 20:31:28.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/b2c2/skystar2.c	2004-03-31 11:55:31.000000000 +0200
@@ -2242,7 +2243,7 @@
 	if (driver_initialize(pdev) != 0)
 		return -ENODEV;
 
-	dvb_register_adapter(&dvb_adapter, skystar2_pci_driver.name);
+	dvb_register_adapter(&dvb_adapter, skystar2_pci_driver.name, THIS_MODULE);
 
 	if (dvb_adapter == NULL) {
 		printk("%s: Error registering DVB adapter\n", __FUNCTION__);
@@ -2342,6 +2343,8 @@
 	{0,},
 };
 
+MODULE_DEVICE_TABLE(pci, skystar2_pci_tbl);
+
 static struct pci_driver skystar2_pci_driver = {
 	.name = "Technisat SkyStar2 driver",
 	.id_table = skystar2_pci_tbl,
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/bt8xx/dvb-bt8xx.c linux-2.6.5-patched/drivers/media/dvb/bt8xx/dvb-bt8xx.c
--- xx-linux-2.6.5/drivers/media/dvb/bt8xx/dvb-bt8xx.c	2004-01-16 18:25:17.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/bt8xx/dvb-bt8xx.c	2004-04-23 21:55:25.000000000 +0200
@@ -286,7 +286,7 @@
 	
 	}
 
-	if ((result = dvb_register_adapter(&card->dvb_adapter, card->card_name)) < 0) {
+	if ((result = dvb_register_adapter(&card->dvb_adapter, card->card_name, THIS_MODULE)) < 0) {
 	
 		printk("dvb_bt8xx: dvb_register_adapter failed (errno = %d)\n", result);
 		
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_demux.c linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_demux.c
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_demux.c	2004-02-22 14:48:47.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_demux.c	2004-03-03 16:48:27.000000000 +0100
@@ -192,7 +194,6 @@
 	struct dvb_demux *demux = feed->demux;
 	struct dvb_demux_filter *f = feed->filter;
 	struct dmx_section_feed *sec = &feed->feed.sec;
-	u8 *buf = sec->secbuf;
 	int section_syntax_indicator;
 
 	if (!sec->is_filtering)
@@ -215,8 +216,6 @@
 
 	sec->seclen = 0;
 
-	memset(buf, 0, DVB_DEMUX_MASK_MAX);
- 
 	return 0;
 }
 
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvbdev.c linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvbdev.c
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvbdev.c	2003-12-18 03:58:18.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvbdev.c	2004-04-23 21:50:41.000000000 +0200
@@ -211,6 +210,8 @@
 	dvbdev->adapter = adap;
 	dvbdev->priv = priv;
 
+	dvbdev->fops->owner = adap->module;
+
 	list_add_tail (&dvbdev->list_head, &adap->device_list);
 
 	devfs_mk_cdev(MKDEV(DVB_MAJOR, nums2minor(adap->num, type, id)),
@@ -227,9 +228,12 @@
 
 void dvb_unregister_device(struct dvb_device *dvbdev)
 {
-	if (dvbdev) {
+	if (!dvbdev)
+		return;
+
 		devfs_remove("dvb/adapter%d/%s%d", dvbdev->adapter->num,
 				dnames[dvbdev->type], dvbdev->id);
+
 		list_del(&dvbdev->list_head);
 		kfree(dvbdev);
 	}
@@ -233,7 +237,6 @@
 		list_del(&dvbdev->list_head);
 		kfree(dvbdev);
 	}
-}
 
 
 static int dvbdev_get_free_adapter_num (void)
@@ -257,7 +260,7 @@
 }
 
 
-int dvb_register_adapter(struct dvb_adapter **padap, const char *name)
+int dvb_register_adapter(struct dvb_adapter **padap, const char *name, struct module *module)
 {
 	struct dvb_adapter *adap;
 	int num;
@@ -281,8 +284,10 @@
 	printk ("DVB: registering new adapter (%s).\n", name);
 	
 	devfs_mk_dir("dvb/adapter%d", num);
+
 	adap->num = num;
 	adap->name = name;
+	adap->module = module;
 
 	list_add_tail (&adap->list_head, &dvb_adapter_list);
 
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvbdev.h linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvbdev.h
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvbdev.h	2003-12-18 03:58:39.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvbdev.h	2004-04-23 21:48:36.000000000 +0200
@@ -48,6 +48,8 @@
 	struct list_head device_list;
 	const char *name;
 	u8 proposed_mac [6];
+
+	struct module *module;
 };
 
 
@@ -75,7 +74,7 @@
 };
 
 
-extern int dvb_register_adapter (struct dvb_adapter **padap, const char *name);
+extern int dvb_register_adapter (struct dvb_adapter **padap, const char *name, struct module *module);
 extern int dvb_unregister_adapter (struct dvb_adapter *adap);
 
 extern int dvb_register_device (struct dvb_adapter *adap,
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_frontend.c linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_frontend.c
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_frontend.c	2004-03-12 20:31:28.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_frontend.c	2004-04-23 21:51:35.000000000 +0200
@@ -6,6 +6,8 @@
  *                         Holger Waechtler 
  *                                    for convergence integrated media GmbH
  *
+ * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup)
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
@@ -37,9 +39,41 @@
 #include "dvbdev.h"
 #include "dvb_functions.h"
 
+#define FESTATE_IDLE 1
+#define FESTATE_RETUNE 2
+#define FESTATE_TUNING_FAST 4
+#define FESTATE_TUNING_SLOW 8
+#define FESTATE_TUNED 16
+#define FESTATE_ZIGZAG_FAST 32
+#define FESTATE_ZIGZAG_SLOW 64
+#define FESTATE_DISEQC 128
+#define FESTATE_WAITFORLOCK (FESTATE_TUNING_FAST | FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW | FESTATE_DISEQC)
+#define FESTATE_SEARCHING_FAST (FESTATE_TUNING_FAST | FESTATE_ZIGZAG_FAST)
+#define FESTATE_SEARCHING_SLOW (FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_SLOW)
+#define FESTATE_LOSTLOCK (FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW)
+/*
+ * FESTATE_IDLE. No tuning parameters have been supplied and the loop is idling.
+ * FESTATE_RETUNE. Parameters have been supplied, but we have not yet performed the first tune.
+ * FESTATE_TUNING_FAST. Tuning parameters have been supplied and fast zigzag scan is in progress.
+ * FESTATE_TUNING_SLOW. Tuning parameters have been supplied. Fast zigzag failed, so we're trying again, but slower.
+ * FESTATE_TUNED. The frontend has successfully locked on.
+ * FESTATE_ZIGZAG_FAST. The lock has been lost, and a fast zigzag has been initiated to try and regain it.
+ * FESTATE_ZIGZAG_SLOW. The lock has been lost. Fast zigzag has been failed, so we're trying again, but slower.
+ * FESTATE_DISEQC. A DISEQC command has just been issued.
+ * FESTATE_WAITFORLOCK. When we're waiting for a lock.
+ * FESTATE_SEARCHING_FAST. When we're searching for a signal using a fast zigzag scan.
+ * FESTATE_SEARCHING_SLOW. When we're searching for a signal using a slow zigzag scan.
+ * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again.
+ */
+
 
 static int dvb_frontend_debug = 0;
 static int dvb_shutdown_timeout = 5;
+static int dvb_override_frequency_bending = 0;
+static int dvb_force_auto_inversion = 0;
+static int dvb_override_tune_delay = 0;
+
+static int do_frequency_bending = 0;
 
 #define dprintk if (dvb_frontend_debug) printk
 
@@ -66,13 +100,18 @@
 	wait_queue_head_t wait_queue;
 	pid_t thread_pid;
 	unsigned long release_jiffies;
-	unsigned long lost_sync_jiffies;
-	int acquire_signal;
+	int state;
 	int bending;
 	int lnb_drift;
-	int timeout_count;
-	int lost_sync_count;
+	int inversion;
+	int auto_step;
+	int auto_sub_step;
+	int started_auto_step;
+	int min_delay;
+	int max_drift;
+	int step_size;
 	int exit;
+	int wakeup;
         fe_status_t status;
 };
 
@@ -170,7 +209,7 @@
 		frequency += this_fe->lnb_drift;
 		frequency += this_fe->bending;
 
-		if (this_fe != fe && fe->lost_sync_count != -1 &&
+		if (this_fe != fe && (fe->state != FESTATE_IDLE) &&
                     frequency > f - stepsize && frequency < f + stepsize)
 		{
 			if (recursive % 2)
@@ -193,9 +232,6 @@
 {
 	dprintk ("%s\n", __FUNCTION__);
 
-	if ((fe->status & FE_HAS_LOCK) && !(s & FE_HAS_LOCK))
-		fe->lost_sync_jiffies = jiffies;
-
 	if (((s ^ fe->status) & FE_HAS_LOCK) && (s & FE_HAS_LOCK))
 		dvb_delay (fe->info->notifier_delay);
 
@@ -293,40 +329,6 @@
         return 0;
 }
 
-
-static int dvb_frontend_set_parameters (struct dvb_frontend_data *fe,
-				 struct dvb_frontend_parameters *param,
-				 int first_trial)
-{
-	struct dvb_frontend *frontend = &fe->frontend;
-	int err;
-
-	if (first_trial) {
-		fe->timeout_count = 0;
-		fe->lost_sync_count = 0;
-		fe->lost_sync_jiffies = jiffies;
-		fe->lnb_drift = 0;
-		fe->acquire_signal = 1;
-		if (fe->status & ~FE_TIMEDOUT)
-			dvb_frontend_add_event (fe, 0);
-		memcpy (&fe->parameters, param,
-			sizeof (struct dvb_frontend_parameters));
-	}
-
-	dvb_bend_frequency (fe, 0);
-
-	dprintk ("%s: f == %i, drift == %i\n",
-		 __FUNCTION__, (int) param->frequency, (int) fe->lnb_drift);
-
-	param->frequency += fe->lnb_drift + fe->bending;
-	err = dvb_frontend_internal_ioctl (frontend, FE_SET_FRONTEND, param);
-	param->frequency -= fe->lnb_drift + fe->bending;
-
-	wake_up_interruptible (&fe->wait_queue);
-
-	return err;
-}
-
 static void dvb_frontend_init (struct dvb_frontend_data *fe)
 {
 	struct dvb_frontend *frontend = &fe->frontend;
@@ -338,8 +340,7 @@
 	dvb_frontend_internal_ioctl (frontend, FE_INIT, NULL);
 }
 
-
-static void update_delay (int *quality, int *delay, int locked)
+static void update_delay (int *quality, int *delay, int min_delay, int locked)
 {
 	int q2;
 
@@ -353,59 +354,101 @@
 	q2 = *quality - 128;
 	q2 *= q2;
 
-	*delay = HZ/20 + q2 * HZ / (128*128);
+	    *delay = min_delay + q2 * HZ / (128*128);
 }
 
-
-#define LNB_DRIFT 1024  /*  max. tolerated LNB drift, XXX FIXME: adjust! */
-#define TIMEOUT 2*HZ
-
 /**
- *  here we only come when we have lost the lock bit, 
- *  let's try to do something useful...
+ * Performs automatic twiddling of frontend parameters.
+ * 
+ * @param fe The frontend concerned.
+ * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT
+ * @returns Number of complete iterations that have been performed.
  */
-static void dvb_frontend_recover (struct dvb_frontend_data *fe)
+static int dvb_frontend_autotune(struct dvb_frontend_data *fe, int check_wrapped)
 {
-	int j = fe->lost_sync_count;
-	int stepsize;
+	int autoinversion;
+	int ready = 0;
+	int original_inversion = fe->parameters.inversion;
+	u32 original_frequency = fe->parameters.frequency;
+
+	// are we using autoinversion?
+	autoinversion = ((!(fe->info->caps & FE_CAN_INVERSION_AUTO)) && (fe->parameters.inversion == INVERSION_AUTO));
+
+	// setup parameters correctly
+	while(!ready) {
+		// calculate the lnb_drift
+		fe->lnb_drift = fe->auto_step * fe->step_size;
+
+		// wrap the auto_step if we've exceeded the maximum drift
+		if (fe->lnb_drift > fe->max_drift) {
+			fe->auto_step = 0;
+			fe->auto_sub_step = 0;
+			fe->lnb_drift = 0;
+		}
 
-	dprintk ("%s\n", __FUNCTION__);
+		// perform inversion and +/- zigzag
+		switch(fe->auto_sub_step) {
+		case 0:
+			// try with the current inversion and current drift setting
+			ready = 1;
+			break;
 
-#if 0
-	if (fe->timeout_count > 3) {
-		printk ("%s: frontend seems dead, reinitializing...\n",
-			__FUNCTION__);
-		dvb_call_frontend_notifiers (fe, 0);
-		dvb_frontend_internal_ioctl (&fe->frontend, FE_INIT, NULL);
-		dvb_frontend_set_parameters (fe, &fe->parameters, 1);
-		dvb_frontend_add_event (fe, FE_REINIT);
-		fe->lost_sync_jiffies = jiffies;
-		fe->timeout_count = 0;
-		return;
-	}
-#endif
+		case 1:
+			if (!autoinversion) break;
 
-	/**
-	 *  let's start a zigzag scan to compensate LNB drift...
-	 */
-		if (fe->info->type == FE_QPSK)
-			stepsize = fe->parameters.u.qpsk.symbol_rate / 16000;
-		else if (fe->info->type == FE_QAM)
-			stepsize = 0;
-		else
-			stepsize = fe->info->frequency_stepsize * 2;
+			fe->inversion = (fe->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF;
+			ready = 1;
+			break;
 
-		if (j % 32 == 0) {
-			fe->lnb_drift = 0;
-		} else {
+		case 2:
+			if (fe->lnb_drift == 0) break;
+		    
+			fe->lnb_drift = -fe->lnb_drift;
+			ready = 1;
+			break;
+	    
+		case 3:
+			if (fe->lnb_drift == 0) break;
+			if (!autoinversion) break;
+		    
+			fe->inversion = (fe->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF;
 			fe->lnb_drift = -fe->lnb_drift;
-			if (j % 2)
-				fe->lnb_drift += stepsize;
+			ready = 1;
+			break;
+		    
+		default:
+			fe->auto_step++;
+			fe->auto_sub_step = -1; // it'll be incremented to 0 in a moment
+			break;
+		}
+	    
+		if (!ready) fe->auto_sub_step++;
+	}
+
+	// if this attempt would hit where we started, indicate a complete iteration has occurred
+	if ((fe->auto_step == fe->started_auto_step) && (fe->auto_sub_step == 0) && check_wrapped) {
+		return 1;
 		}
 
-		dvb_frontend_set_parameters (fe, &fe->parameters, 0);
+	// perform frequency bending if necessary
+	if ((dvb_override_frequency_bending != 1) && do_frequency_bending)
+		dvb_bend_frequency(fe, 0);
+
+	// instrumentation
+	dprintk("%s: drift:%i bending:%i inversion:%i auto_step:%i auto_sub_step:%i started_auto_step:%i\n", 
+		__FUNCTION__, fe->lnb_drift, fe->bending, fe->inversion, fe->auto_step, fe->auto_sub_step,
+		fe->started_auto_step);
+    
+	// set the frontend itself
+	fe->parameters.frequency += fe->lnb_drift + fe->bending;
+	if (autoinversion) fe->parameters.inversion = fe->inversion;
+	dvb_frontend_internal_ioctl (&fe->frontend, FE_SET_FRONTEND, &fe->parameters);
+	fe->parameters.frequency = original_frequency;
+	fe->parameters.inversion = original_inversion;
 
-	dvb_frontend_internal_ioctl (&fe->frontend, FE_RESET, NULL);
+	// normal return
+	fe->auto_sub_step++;
+	return 0;
 }
 
 
@@ -422,6 +465,19 @@
 	return 0;
 }
 
+static int dvb_frontend_should_wakeup (struct dvb_frontend_data *fe)
+{
+	if (fe->wakeup) {
+		fe->wakeup = 0;
+		return 1;
+	}
+	return dvb_frontend_is_exiting(fe);
+}
+
+static void dvb_frontend_wakeup (struct dvb_frontend_data *fe) {
+	fe->wakeup = 1;
+	wake_up_interruptible(&fe->wait_queue);
+}
 
 static int dvb_frontend_thread (void *data)
 {
@@ -430,6 +486,7 @@
 	char name [15];
 	int quality = 0, delay = 3*HZ;
 	fe_status_t s;
+	int check_wrapped = 0;
 
 	dprintk ("%s\n", __FUNCTION__);
 
@@ -438,15 +495,14 @@
 
 	dvb_kernel_thread_setup (name);
 
-	fe->lost_sync_count = -1;
-
 	dvb_call_frontend_notifiers (fe, 0);
 	dvb_frontend_init (fe);
+	fe->wakeup = 0;
 
 	while (1) {
 		up (&fe->sem);      /* is locked when we enter the thread... */
 
-		timeout = wait_event_interruptible_timeout(fe->wait_queue,0 != dvb_frontend_is_exiting (fe), delay);
+		timeout = wait_event_interruptible_timeout(fe->wait_queue,0 != dvb_frontend_should_wakeup (fe), delay);
 		if (-ERESTARTSYS == timeout || 0 != dvb_frontend_is_exiting (fe)) {
 			/* got signal or quitting */
 			break;
@@ -455,43 +511,104 @@
 		if (down_interruptible (&fe->sem))
 			break;
 
-		if (fe->lost_sync_count == -1)
+		// if we've got no parameters, just keep idling
+		if (fe->state & FESTATE_IDLE) {
+			delay = 3*HZ;
+			quality = 0;
 			continue;
+		}
 
+		// get the frontend status
 		dvb_frontend_internal_ioctl (&fe->frontend, FE_READ_STATUS, &s);
+		if (s != fe->status)
+			dvb_frontend_add_event (fe, s);
 
-		update_delay (&quality, &delay, s & FE_HAS_LOCK);
+		// if we're not tuned, and we have a lock, move to the TUNED state
+		if ((fe->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) {
+			update_delay(&quality, &delay, fe->min_delay, s & FE_HAS_LOCK);
+			fe->state = FESTATE_TUNED;
+
+			// if we're tuned, then we have determined the correct inversion
+			if ((!(fe->info->caps & FE_CAN_INVERSION_AUTO)) && (fe->parameters.inversion == INVERSION_AUTO)) {
+				fe->parameters.inversion = fe->inversion;
+			}
+			continue;
+		}
 
-		s &= ~FE_TIMEDOUT;
+		// if we are tuned already, check we're still locked
+		if (fe->state & FESTATE_TUNED) {
+			update_delay(&quality, &delay, fe->min_delay, s & FE_HAS_LOCK);
 
+			// we're tuned, and the lock is still good...
 		if (s & FE_HAS_LOCK) {
-			fe->timeout_count = 0;
-			fe->lost_sync_count = 0;
-			fe->acquire_signal = 0;
+				continue;
 		} else {
-			fe->lost_sync_count++;
-			if (!(fe->info->caps & FE_CAN_RECOVER)) {
-				if (!(fe->info->caps & FE_CAN_CLEAN_SETUP)) {
-					if (fe->lost_sync_count < 10) {
-						if (fe->acquire_signal)
-							dvb_frontend_internal_ioctl(
-									&fe->frontend,
-									FE_RESET, NULL);
+				// if we _WERE_ tuned, but now don't have a lock, need to zigzag
+				fe->state = FESTATE_ZIGZAG_FAST;
+				fe->started_auto_step = fe->auto_step;
+				check_wrapped = 0;
+				// fallthrough
+			}
+		}
+
+		// don't actually do anything if we're in the LOSTLOCK state, the frontend is set to
+		// FE_CAN_RECOVER, and the max_drift is 0
+		if ((fe->state & FESTATE_LOSTLOCK) && 
+		    (fe->info->caps & FE_CAN_RECOVER) && (fe->max_drift == 0)) {
+			update_delay(&quality, &delay, fe->min_delay, s & FE_HAS_LOCK);
 						continue;
 				}
+	    
+		// don't do anything if we're in the DISEQC state, since this might be someone
+		// with a motorized dish controlled by DISEQC. If its actually a re-tune, there will
+		// be a SET_FRONTEND soon enough.
+		if (fe->state & FESTATE_DISEQC) {
+			update_delay(&quality, &delay, fe->min_delay, s & FE_HAS_LOCK);
+			continue;
 				}
-				dvb_frontend_recover (fe);
-				delay = HZ/5;
+
+		// if we're in the RETUNE state, set everything up for a brand new scan,
+		// keeping the current inversion setting, as the next tune is _very_ likely
+		// to require the same
+		if (fe->state & FESTATE_RETUNE) {
+			fe->lnb_drift = 0;
+			fe->auto_step = 0;
+			fe->auto_sub_step = 0;
+			fe->started_auto_step = 0;
+			check_wrapped = 0;
+		}
+
+		// fast zigzag.
+		if ((fe->state & FESTATE_SEARCHING_FAST) || (fe->state & FESTATE_RETUNE)) {
+			delay = fe->min_delay;
+
+			// peform a tune
+			if (dvb_frontend_autotune(fe, check_wrapped)) {
+				// OK, if we've run out of trials at the fast speed. Drop back to
+				// slow for the _next_ attempt
+				fe->state = FESTATE_SEARCHING_SLOW;
+				fe->started_auto_step = fe->auto_step;
+				continue;
 			}
-			if (jiffies - fe->lost_sync_jiffies > TIMEOUT) {
-				s |= FE_TIMEDOUT;
-				if ((fe->status & FE_TIMEDOUT) == 0)
-					fe->timeout_count++;
+			check_wrapped = 1;
+
+			// if we've just retuned, enter the ZIGZAG_FAST state. This ensures
+			// we cannot return from an FE_SET_FRONTEND ioctl before the first frontend
+			// tune occurs
+			if (fe->state & FESTATE_RETUNE) {
+				fe->state = FESTATE_TUNING_FAST;
+				wake_up_interruptible(&fe->wait_queue);
 			}
 		}
 
-		if (s != fe->status)
-			dvb_frontend_add_event (fe, s);
+		// slow zigzag
+		if (fe->state & FESTATE_SEARCHING_SLOW) {
+			update_delay(&quality, &delay, fe->min_delay, s & FE_HAS_LOCK);
+		    
+			// Note: don't bother checking for wrapping; we stay in this state 
+			// until we get a lock
+			dvb_frontend_autotune(fe, 0);
+		}
 	};
 
 	if (dvb_shutdown_timeout)
@@ -502,7 +619,7 @@
 	fe->thread_pid = 0;
 	mb();
 
-	wake_up_interruptible (&fe->wait_queue);
+	dvb_frontend_wakeup(fe);
 	return 0;
 }
 
@@ -529,13 +646,15 @@
 	}
 
 	/* wake up the frontend thread, so it notices that fe->exit == 1 */
-		wake_up_interruptible (&fe->wait_queue);
+	dvb_frontend_wakeup(fe);
 
 	/* wait until the frontend thread has exited */
 	ret = wait_event_interruptible(fe->wait_queue,0 == fe->thread_pid);
 	if (-ERESTARTSYS != ret) {
+		fe->state = FESTATE_IDLE;
 		return;
 	}
+	fe->state = FESTATE_IDLE;
 
 	/* paranoia check in case a signal arrived */
 	if (fe->thread_pid)
@@ -562,6 +681,7 @@
 	if (down_interruptible (&fe->sem))
 		return -EINTR;
 
+	fe->state = FESTATE_IDLE;
 	fe->exit = 0;
 	fe->thread_pid = 0;
 	mb();
@@ -583,6 +703,7 @@
 {
 	struct dvb_device *dvbdev = file->private_data;
 	struct dvb_frontend_data *fe = dvbdev->priv;
+	struct dvb_frontend_tune_settings fetunesettings;
 	int err = 0;
 
 	dprintk ("%s\n", __FUNCTION__);
@@ -600,13 +721,59 @@
 		if (fe->status)
 			dvb_call_frontend_notifiers (fe, 0);
 		dvb_frontend_internal_ioctl (&fe->frontend, cmd, parg);
+		fe->state = FESTATE_DISEQC;
 		break;
+
 	case FE_SET_FRONTEND:
-		err = dvb_frontend_set_parameters (fe, parg, 1);
-		set_current_state(TASK_INTERRUPTIBLE);
-		schedule_timeout(1);
-		wake_up_interruptible(&fe->wait_queue);
+		fe->state = FESTATE_RETUNE;
+	    
+		memcpy (&fe->parameters, parg,
+			sizeof (struct dvb_frontend_parameters));
+
+		memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings));
+		memcpy(&fetunesettings.parameters, parg,
+		       sizeof (struct dvb_frontend_parameters));
+		    
+		// force auto frequency inversion if requested
+		if (dvb_force_auto_inversion) {
+			fe->parameters.inversion = INVERSION_AUTO;
+			fetunesettings.parameters.inversion = INVERSION_AUTO;
+		}
+
+		// get frontend-specific tuning settings
+		if (dvb_frontend_internal_ioctl(&fe->frontend, FE_GET_TUNE_SETTINGS, &fetunesettings) == 0) {
+			fe->min_delay = (fetunesettings.min_delay_ms * HZ) / 1000;
+			fe->max_drift = fetunesettings.max_drift;
+			fe->step_size = fetunesettings.step_size;
+		} else {
+			// default values
+			switch(fe->info->type) {
+			case FE_QPSK:
+				fe->min_delay = HZ/20; // default mindelay of 50ms
+				fe->step_size = fe->parameters.u.qpsk.symbol_rate / 16000;
+				fe->max_drift = fe->parameters.u.qpsk.symbol_rate / 2000;
 		break;
+			    
+			case FE_QAM:
+				fe->min_delay = HZ/20; // default mindelay of 50ms
+				fe->step_size = 0;
+				fe->max_drift = 0; // don't want any zigzagging under DVB-C frontends
+				break;
+			    
+			case FE_OFDM:
+				fe->min_delay = HZ/20; // default mindelay of 50ms
+				fe->step_size = fe->info->frequency_stepsize * 2;
+				fe->max_drift = (fe->info->frequency_stepsize * 2) + 1;
+				break;
+			}
+		}
+		if (dvb_override_tune_delay > 0) {
+		       fe->min_delay = (dvb_override_tune_delay * HZ) / 1000;
+		}
+
+		dvb_frontend_add_event (fe, 0);	    
+		break;
+
 	case FE_GET_EVENT:
 		err = dvb_frontend_get_event (fe, parg, file->f_flags);
 		break;
@@ -615,10 +783,26 @@
 			sizeof (struct dvb_frontend_parameters));
 		/*  fall-through... */
 	default:
-		dvb_frontend_internal_ioctl (&fe->frontend, cmd, parg);
+		err = dvb_frontend_internal_ioctl (&fe->frontend, cmd, parg);
 	};
 
 	up (&fe->sem);
+	if (err < 0)
+		return err;
+
+	// Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't do it, it is done for it.
+	if ((cmd == FE_GET_INFO) && (err == 0)) {
+		struct dvb_frontend_info* tmp = (struct dvb_frontend_info*) parg;
+		tmp->caps |= FE_CAN_INVERSION_AUTO;
+	}
+
+	// if the frontend has just been set, wait until the first tune has finished.
+	// This ensures the app doesn't start reading data too quickly, perhaps from the
+	// previous lock, which is REALLY CONFUSING TO DEBUG!
+	if ((cmd == FE_SET_FRONTEND) && (err == 0)) {
+		dvb_frontend_wakeup(fe);
+		err = wait_event_interruptible(fe->wait_queue, fe->state & ~FESTATE_RETUNE);
+	}
 
 	return err;
 }
@@ -915,6 +1099,7 @@
 	fe->frontend.i2c = i2c;
 	fe->frontend.data = data;
 	fe->info = info;
+	fe->inversion = INVERSION_OFF;
 
 	list_for_each (entry, &frontend_ioctl_list) {
 		struct dvb_frontend_ioctl_data *ioctl;
@@ -954,6 +1139,9 @@
 	dvb_register_device (i2c->adapter, &fe->dvbdev, &dvbdev_template,
 			     fe, DVB_DEVICE_FRONTEND);
 
+	if ((info->caps & FE_NEEDS_BENDING) || (dvb_override_frequency_bending == 2))
+		do_frequency_bending = 1;
+    
 	up (&frontend_mutex);
 
 	return 0;
@@ -991,6 +1179,12 @@
 
 MODULE_PARM(dvb_frontend_debug,"i");
 MODULE_PARM(dvb_shutdown_timeout,"i");
+MODULE_PARM(dvb_override_frequency_bending,"i");
+MODULE_PARM(dvb_force_auto_inversion,"i");
+MODULE_PARM(dvb_override_tune_delay,"i");
+
 MODULE_PARM_DESC(dvb_frontend_debug, "enable verbose debug messages");
 MODULE_PARM_DESC(dvb_shutdown_timeout, "wait <shutdown_timeout> seconds after close() before suspending hardware");
-
+MODULE_PARM_DESC(dvb_override_frequency_bending, "0: normal (default), 1: never use frequency bending, 2: always use frequency bending");
+MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always");
+MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt");
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_frontend.h linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_frontend.h
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_frontend.h	2003-12-18 03:59:42.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_frontend.h	2004-03-11 19:40:44.000000000 +0100
@@ -56,14 +56,25 @@
 	void *data;                /*  can be used by hardware module... */
 };
 
+struct dvb_frontend_tune_settings {
+        int min_delay_ms;
+        int step_size;
+        int max_drift;
+        struct dvb_frontend_parameters parameters;
+};
+
 
 /**
  *   private frontend command ioctl's.
  *   keep them in sync with the public ones defined in linux/dvb/frontend.h
+ * 
+ *   FE_SLEEP. Ioctl used to put frontend into a low power mode.
+ *   FE_INIT. Ioctl used to initialise the frontend.
+ *   FE_GET_TUNE_SETTINGS. Get the frontend-specific tuning loop settings for the supplied set of parameters.
  */
 #define FE_SLEEP              _IO('v', 80)
 #define FE_INIT               _IO('v', 81)
-#define FE_RESET              _IO('v', 82)
+#define FE_GET_TUNE_SETTINGS  _IOWR('v', 83, struct dvb_frontend_tune_settings)
 
 
 extern int
diff -urawBN xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_net.c linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_net.c
--- xx-linux-2.6.5/drivers/media/dvb/dvb-core/dvb_net.c	2004-03-12 20:31:28.000000000 +0100
+++ linux-2.6.5-patched/drivers/media/dvb/dvb-core/dvb_net.c	2004-04-23 21:52:51.000000000 +0200
@@ -5,33 +5,53 @@
  *                    Ralph Metzler <ralph@convergence.de>
  * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de>
  *
+ * ULE Decapsulation code:
+ * Copyright (C) 2003 gcs - Global Communication & Services GmbH.
+ *                and Institute for Computer Sciences
+ *                    Salzburg University.
+ *                    Hilmar Linder <hlinder@cosy.sbg.ac.at>
+ *                and Wolfram Stering <wstering@cosy.sbg.ac.at>
+ *
+ * ULE Decaps according to draft-fair-ipdvb-ule-01.txt.
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
  * 
- *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  * 
- *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
- * 
  */
 
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 #include <linux/dvb/net.h>
+#include <linux/uio.h>
 #include <asm/uaccess.h>
+#include <linux/crc32.h>
 
 #include "dvb_demux.h"
 #include "dvb_net.h"
 #include "dvb_functions.h"
 
 
+static inline __u32 iov_crc32( __u32 c, struct iovec *iov, unsigned int cnt )
+{
+	unsigned int j;
+	for (j = 0; j < cnt; j++)
+		c = crc32_be( c, iov[j].iov_base, iov[j].iov_len );
+	return c;
+}
+
+
 #if 1
 #define dprintk(x...) printk(x)
 #else
@@ -41,14 +61,46 @@
 
 #define DVB_NET_MULTICAST_MAX 10
 
+#define isprint(c)	((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
+
+static void hexdump( const unsigned char *buf, unsigned short len )
+{
+	char str[80], octet[10];
+	int ofs, i, l;
+
+	for (ofs = 0; ofs < len; ofs += 16) {
+		sprintf( str, "%03d: ", ofs );
+
+		for (i = 0; i < 16; i++) {
+			if ((i + ofs) < len)
+				sprintf( octet, "%02x ", buf[ofs + i] );
+			else
+				strcpy( octet, "   " );
+
+			strcat( str, octet );
+		}
+		strcat( str, "  " );
+		l = strlen( str );
+
+		for (i = 0; (i < 16) && ((i + ofs) < len); i++)
+			str[l++] = isprint( buf[ofs + i] ) ? buf[ofs + i] : '.';
+
+		str[l] = '\0';
+		printk( KERN_WARNING "%s\n", str );
+	}
+}
+
+
 struct dvb_net_priv {
 	int in_use;
         struct net_device_stats stats;
         char name[6];
 	u16 pid;
+	struct dvb_net *host;
         struct dmx_demux *demux;
 	struct dmx_section_feed *secfeed;
 	struct dmx_section_filter *secfilter;
+	struct dmx_ts_feed *tsfeed;
 	int multi_num;
 	struct dmx_section_filter *multi_secfilter[DVB_NET_MULTICAST_MAX];
 	unsigned char multi_macs[DVB_NET_MULTICAST_MAX][6];
@@ -59,6 +111,18 @@
 #define RX_MODE_PROMISC 3
 	struct work_struct set_multicast_list_wq;
 	struct work_struct restart_net_feed_wq;
+	unsigned char feedtype;
+	int need_pusi;
+	unsigned char tscc;			/* TS continuity counter after sync. */
+	struct sk_buff *ule_skb;
+	unsigned short ule_sndu_len;
+	unsigned short ule_sndu_type;
+	unsigned char ule_sndu_type_1;
+	unsigned char ule_dbit;			/* whether the DestMAC address present
+						 * bit is set or not. */
+	unsigned char ule_ethhdr_complete;	/* whether we have completed the Ethernet
+						 * header for the current ULE SNDU. */
+	int ule_sndu_remain;
 };
 
 
@@ -107,35 +171,442 @@
 	return htons(ETH_P_802_2);
 }
 
+#define TS_SZ	188
+#define TS_SYNC	0x47
+#define TS_TEI	0x80
+#define TS_PUSI	0x40
+#define TS_AF_A	0x20
+#define TS_AF_D	0x10
+
+#define ULE_TEST	0
+#define ULE_BRIDGED	1
+#define ULE_LLC		2
+
+static inline void reset_ule( struct dvb_net_priv *p )
+{
+	p->ule_skb = NULL;
+	p->ule_sndu_len = 0;
+	p->ule_sndu_type = 0;
+	p->ule_sndu_type_1 = 0;
+	p->ule_sndu_remain = 0;
+	p->ule_dbit = 0xFF;
+	p->ule_ethhdr_complete = 0;
+}
+
+static const char eth_dest_addr[] = { 0x0b, 0x0a, 0x09, 0x08, 0x04, 0x03 };
+
+static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len )
+{
+	struct dvb_net_priv *priv = (struct dvb_net_priv *)dev->priv;
+	unsigned long skipped = 0L, skblen = 0L;
+	u8 *ts, *ts_end, *from_where = NULL, ts_remain = 0, how_much = 0, new_ts = 1;
+	struct ethhdr *ethh = NULL;
+	unsigned int emergency_count = 0;
+
+	if (dev == NULL) {
+		printk( KERN_ERR "NO netdev struct!\n" );
+		return;
+	}
+
+	for (ts = (char *)buf, ts_end = (char *)buf + buf_len; ts < ts_end; ) {
+
+		if (emergency_count++ > 200) {
+			/* Huh?? */
+			hexdump(ts, TS_SZ);
+			printk(KERN_WARNING "*** LOOP ALERT! ts %p ts_remain %u "
+				"how_much %u, ule_skb %p, ule_len %u, ule_remain %u\n",
+				ts, ts_remain, how_much, priv->ule_skb,
+				priv->ule_sndu_len, priv->ule_sndu_remain);
+			break;
+		}
+
+		if (new_ts) {
+			if ((ts[0] != TS_SYNC) || (ts[1] & TS_TEI)) {
+				printk(KERN_WARNING "Invalid TS cell: SYNC %#x, TEI %u.\n",
+				       ts[0], ts[1] & TS_TEI >> 7);
+				continue;
+			}
+			ts_remain = 184;
+			from_where = ts + 4;
+		}
+		/* Synchronize on PUSI, if required. */
+		if (priv->need_pusi) {
+			if (ts[1] & TS_PUSI) {
+				/* Find beginning of first ULE SNDU in current TS cell.
+				 * priv->need_pusi = 0; */
+				priv->tscc = ts[3] & 0x0F;
+				/* There is a pointer field here. */
+				if (ts[4] > ts_remain) {
+					printk(KERN_ERR "Invalid ULE packet "
+					       "(pointer field %d)\n", ts[4]);
+					continue;
+				}
+				from_where = &ts[5] + ts[4];
+				ts_remain -= 1 + ts[4];
+				skipped = 0;
+			} else {
+				skipped++;
+				continue;
+			}
+		}
+
+		/* Check continuity counter. */
+		if (new_ts) {
+			if ((ts[3] & 0x0F) == priv->tscc)
+				priv->tscc = (priv->tscc + 1) & 0x0F;
+			else {
+				/* TS discontinuity handling: */
+				if (priv->ule_skb) {
+					dev_kfree_skb( priv->ule_skb );
+					/* Prepare for next SNDU. */
+					reset_ule(priv);
+					((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
+					((struct dvb_net_priv *) dev->priv)->stats.rx_frame_errors++;
+				}
+				/* skip to next PUSI. */
+				printk(KERN_WARNING "TS discontinuity: got %#x, "
+				       "exptected %#x.\n", ts[3] & 0x0F, priv->tscc);
+				priv->need_pusi = 1;
+				continue;
+			}
+			/* If we still have an incomplete payload, but PUSI is
+			 * set, some TS cells are missing.
+			 * This is only possible here, if we missed exactly 16 TS
+			 * cells (continuity counter). */
+			if (ts[1] & TS_PUSI) {
+				if (! priv->need_pusi) {
+					/* printk(KERN_WARNING "Skipping pointer field %u.\n", *from_where); */
+					if (*from_where > 181) {
+						printk(KERN_WARNING "*** Invalid pointer "
+						       "field: %u.  Current TS cell "
+						       "follows:\n", *from_where);
+						hexdump( ts, TS_SZ );
+						printk(KERN_WARNING "-------------------\n");
+					}
+					/* Skip pointer field (we're processing a
+					 * packed payload). */
+					from_where += 1;
+					ts_remain -= 1;
+				} else
+					priv->need_pusi = 0;
+
+				if (priv->ule_sndu_remain > 183) {
+					((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
+					((struct dvb_net_priv *) dev->priv)->stats.rx_length_errors++;
+					printk(KERN_WARNING "Expected %d more SNDU bytes, but "
+					       "got PUSI.  Flushing incomplete payload.\n",
+					       priv->ule_sndu_remain);
+					dev_kfree_skb(priv->ule_skb);
+					/* Prepare for next SNDU. */
+					reset_ule(priv);
+				}
+			}
+		}
+
+		/* Check if new payload needs to be started. */
+		if (priv->ule_skb == NULL) {
+			/* Start a new payload w/ skb.
+			 * Find ULE header.  It is only guaranteed that the
+			 * length field (2 bytes) is contained in the current
+			 * TS.
+			 * Check ts_remain has to be >= 2 here. */
+			if (ts_remain < 2) {
+				printk(KERN_WARNING "Invalid payload packing: only %d "
+				       "bytes left in TS.  Resyncing.\n", ts_remain);
+				priv->ule_sndu_len = 0;
+				priv->need_pusi = 1;
+				continue;
+			}
+
+			if (! priv->ule_sndu_len) {
+				priv->ule_sndu_len = from_where[0] << 8 | from_where[1];
+				if (priv->ule_sndu_len & 0x8000) {
+					/* D-Bit is set: no dest mac present. */
+					priv->ule_sndu_len &= 0x7FFF;
+					priv->ule_dbit = 1;
+				} else
+					priv->ule_dbit = 0;
+
+				/* printk(KERN_WARNING "ULE D-Bit: %d, SNDU len %u.\n",
+				          priv->ule_dbit, priv->ule_sndu_len); */
+
+				if (priv->ule_sndu_len > 32763) {
+					printk(KERN_WARNING "Invalid ULE SNDU length %u. "
+					       "Resyncing.\n", priv->ule_sndu_len);
+					hexdump(ts, TS_SZ);
+					priv->ule_sndu_len = 0;
+					priv->need_pusi = 1;
+					new_ts = 1;
+					ts += TS_SZ;
+					continue;
+				}
+				ts_remain -= 2;	/* consume the 2 bytes SNDU length. */
+				from_where += 2;
+			}
+
+			/*
+			 * State of current TS:
+			 *   ts_remain (remaining bytes in the current TS cell)
+			 *   0	ule_type is not available now, we need the next TS cell
+			 *   1	the first byte of the ule_type is present
+			 * >=2	full ULE header present, maybe some payload data as well.
+			 */
+			switch (ts_remain) {
+				case 1:
+					priv->ule_sndu_type = from_where[0] << 8;
+					priv->ule_sndu_type_1 = 1; /* first byte of ule_type is set. */
+					/* ts_remain -= 1; from_where += 1;
+					 *   here not necessary, because we continue. */
+				case 0:
+					new_ts = 1;
+					ts += TS_SZ;
+					continue;
+
+				default: /* complete ULE header is present in current TS. */
+					/* Extract ULE type field. */
+					if (priv->ule_sndu_type_1) {
+						priv->ule_sndu_type |= from_where[0];
+						from_where += 1; /* points to payload start. */
+						ts_remain -= 1;
+					} else {
+						/* Complete type is present in new TS. */
+						priv->ule_sndu_type = from_where[0] << 8 | from_where[1];
+						from_where += 2; /* points to payload start. */
+						ts_remain -= 2;
+					}
+					break;
+			}
+
+			if (priv->ule_sndu_type == ULE_TEST) {
+				/* Test SNDU, discarded by the receiver. */
+				printk(KERN_WARNING "Discarding ULE Test SNDU (%d bytes). "
+				       "Resyncing.\n", priv->ule_sndu_len);
+				priv->ule_sndu_len = 0;
+				priv->need_pusi = 1;
+				continue;
+			}
+
+			skblen = priv->ule_sndu_len;	/* Including CRC32 */
+			if (priv->ule_sndu_type != ULE_BRIDGED) {
+				skblen += ETH_HLEN;
+#if 1
+				if (! priv->ule_dbit)
+					skblen -= ETH_ALEN;
+#endif
+			}
+			priv->ule_skb = dev_alloc_skb(skblen);
+			if (priv->ule_skb == NULL) {
+				printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n",
+				       dev->name);
+				((struct dvb_net_priv *)dev->priv)->stats.rx_dropped++;
+				return;
+			}
+
+#if 0
+			if (priv->ule_sndu_type != ULE_BRIDGED) {
+				// skb_reserve(priv->ule_skb, 2);    /* longword align L3 header */
+				// Create Ethernet header.
+				ethh = (struct ethhdr *)skb_put( priv->ule_skb, ETH_HLEN );
+				memset( ethh->h_source, 0x00, ETH_ALEN );
+				if (priv->ule_dbit) {
+					// Dest MAC address not present --> generate our own.
+					memcpy( ethh->h_dest, eth_dest_addr, ETH_ALEN );
+				} else {
+					// Dest MAC address could be split across two TS cells.
+					// FIXME: implement.
+
+					printk( KERN_WARNING "%s: got destination MAC "
+						"address.\n", dev->name );
+					memcpy( ethh->h_dest, eth_dest_addr, ETH_ALEN );
+				}
+				ethh->h_proto = htons(priv->ule_sndu_type == ULE_LLC ?
+						      priv->ule_sndu_len : priv->ule_sndu_type);
+			}
+#endif
+			/* this includes the CRC32 _and_ dest mac, if !dbit! */
+			priv->ule_sndu_remain = priv->ule_sndu_len;
+			priv->ule_skb->dev = dev;
+		}
+
+		/* Copy data into our current skb. */
+		how_much = min(priv->ule_sndu_remain, (int)ts_remain);
+		if ((priv->ule_ethhdr_complete < ETH_ALEN) &&
+		    (priv->ule_sndu_type != ULE_BRIDGED)) {
+			ethh = (struct ethhdr *)priv->ule_skb->data;
+			if (! priv->ule_dbit) {
+				if (how_much >= (ETH_ALEN - priv->ule_ethhdr_complete)) {
+					/* copy dest mac address. */
+					memcpy(skb_put(priv->ule_skb,
+						       (ETH_ALEN - priv->ule_ethhdr_complete)),
+					       from_where,
+					       (ETH_ALEN - priv->ule_ethhdr_complete));
+					memset(ethh->h_source, 0x00, ETH_ALEN);
+					ethh->h_proto = htons(priv->ule_sndu_type == ULE_LLC ?
+							      priv->ule_sndu_len :
+							      priv->ule_sndu_type);
+					skb_put(priv->ule_skb, ETH_ALEN + 2);
+
+					how_much -= (ETH_ALEN - priv->ule_ethhdr_complete);
+					priv->ule_sndu_remain -= (ETH_ALEN -
+								  priv->ule_ethhdr_complete);
+					ts_remain -= (ETH_ALEN - priv->ule_ethhdr_complete);
+					from_where += (ETH_ALEN - priv->ule_ethhdr_complete);
+					priv->ule_ethhdr_complete = ETH_ALEN;
+				}
+			} else {
+				/* Generate whole Ethernet header. */
+				memcpy(ethh->h_dest, eth_dest_addr, ETH_ALEN);
+				memset(ethh->h_source, 0x00, ETH_ALEN);
+				ethh->h_proto = htons(priv->ule_sndu_type == ULE_LLC ?
+						      priv->ule_sndu_len : priv->ule_sndu_type);
+				skb_put(priv->ule_skb, ETH_HLEN);
+				priv->ule_ethhdr_complete = ETH_ALEN;
+			}
+		}
+		/* printk(KERN_WARNING "Copying %u bytes, ule_sndu_remain = %u, "
+		          "ule_sndu_len = %u.\n", how_much, priv->ule_sndu_remain,
+			  priv->ule_sndu_len); */
+		memcpy(skb_put(priv->ule_skb, how_much), from_where, how_much);
+		priv->ule_sndu_remain -= how_much;
+		ts_remain -= how_much;
+		from_where += how_much;
+
+		if ((priv->ule_ethhdr_complete < ETH_ALEN) &&
+		    (priv->ule_sndu_type != ULE_BRIDGED)) {
+			priv->ule_ethhdr_complete += how_much;
+		}
+
+		/* Check for complete payload. */
+		if (priv->ule_sndu_remain <= 0) {
+			/* Check CRC32, we've got it in our skb already. */
+			unsigned short ulen = htons(priv->ule_sndu_len);
+			unsigned short utype = htons(priv->ule_sndu_type);
+			struct iovec iov[4] = {
+				{ &ulen, sizeof ulen },
+				{ &utype, sizeof utype },
+				{ NULL, 0 },
+				{ priv->ule_skb->data + ETH_HLEN,
+					priv->ule_skb->len - ETH_HLEN - 4 }
+			};
+			unsigned long ule_crc = ~0L, expected_crc;
+			if (priv->ule_dbit) {
+				/* Set D-bit for CRC32 verification,
+				 * if it was set originally. */
+				ulen |= 0x0080;
+			} else {
+				iov[2].iov_base = priv->ule_skb->data;
+				iov[2].iov_len = ETH_ALEN;
+			}
+			ule_crc = iov_crc32(ule_crc, iov, 4);
+			expected_crc = *((u8 *)priv->ule_skb->tail - 4) << 24 |
+				*((u8 *)priv->ule_skb->tail - 3) << 16 |
+				*((u8 *)priv->ule_skb->tail - 2) << 8 |
+				*((u8 *)priv->ule_skb->tail - 1);
+			if (ule_crc != expected_crc) {
+				printk(KERN_WARNING "CRC32 check %s: %#lx / %#lx.\n",
+				       ule_crc != expected_crc ? "FAILED" : "OK",
+				       ule_crc, expected_crc);
+				hexdump(priv->ule_skb->data + ETH_HLEN,
+					priv->ule_skb->len - ETH_HLEN);
+
+				((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
+				((struct dvb_net_priv *) dev->priv)->stats.rx_crc_errors++;
+				dev_kfree_skb(priv->ule_skb);
+			} else {
+				/* CRC32 was OK. Remove it from skb. */
+				priv->ule_skb->tail -= 4;
+				priv->ule_skb->len -= 4;
+				/* Stuff into kernel's protocol stack. */
+				priv->ule_skb->protocol = dvb_net_eth_type_trans(priv->ule_skb, dev);
+				/* If D-bit is set (i.e. destination MAC address not present),
+				 * receive the packet anyhw. */
+				/* if (priv->ule_dbit && skb->pkt_type == PACKET_OTHERHOST) */
+					priv->ule_skb->pkt_type = PACKET_HOST;
+				((struct dvb_net_priv *) dev->priv)->stats.rx_packets++;
+				((struct dvb_net_priv *) dev->priv)->stats.rx_bytes += priv->ule_skb->len;
+				netif_rx(priv->ule_skb);
+			}
+			/* Prepare for next SNDU. */
+			reset_ule(priv);
+		}
+
+		/* More data in current TS (look at the bytes following the CRC32)? */
+		if (ts_remain >= 2 && *((unsigned short *)from_where) != 0xFFFF) {
+			/* Next ULE SNDU starts right there. */
+			new_ts = 0;
+			priv->ule_skb = NULL;
+			priv->ule_sndu_type_1 = 0;
+			priv->ule_sndu_len = 0;
+			// printk(KERN_WARNING "More data in current TS: [%#x %#x %#x %#x]\n",
+			//	*(from_where + 0), *(from_where + 1),
+			//	*(from_where + 2), *(from_where + 3));
+			// printk(KERN_WARNING "ts @ %p, stopped @ %p:\n", ts, from_where + 0);
+			// hexdump(ts, 188);
+		} else {
+			new_ts = 1;
+			ts += TS_SZ;
+			if (priv->ule_skb == NULL) {
+				priv->need_pusi = 1;
+				priv->ule_sndu_type_1 = 0;
+				priv->ule_sndu_len = 0;
+			}
+		}
+	}	/* for all available TS cells */
+}
+
+static int dvb_net_ts_callback(const u8 *buffer1, size_t buffer1_len,
+			       const u8 *buffer2, size_t buffer2_len,
+			       struct dmx_ts_feed *feed, enum dmx_success success)
+{
+	struct net_device *dev = (struct net_device *)feed->priv;
+
+	if (buffer2 != 0)
+		printk(KERN_WARNING "buffer2 not 0: %p.\n", buffer2);
+	if (buffer1_len > 32768)
+		printk(KERN_WARNING "length > 32k: %u.\n", buffer1_len);
+	/* printk("TS callback: %u bytes, %u TS cells @ %p.\n",
+	          buffer1_len, buffer1_len / TS_SZ, buffer1); */
+	dvb_net_ule(dev, buffer1, buffer1_len);
+	return 0;
+}
+
 
 static void dvb_net_sec(struct net_device *dev, u8 *pkt, int pkt_len)
 {
         u8 *eth;
         struct sk_buff *skb;
+	struct net_device_stats *stats = &(((struct dvb_net_priv *) dev->priv)->stats);
 
 	/* note: pkt_len includes a 32bit checksum */
 	if (pkt_len < 16) {
 		printk("%s: IP/MPE packet length = %d too small.\n",
 			dev->name, pkt_len);
-		((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
-		((struct dvb_net_priv *) dev->priv)->stats.rx_length_errors++;
+		stats->rx_errors++;
+		stats->rx_length_errors++;
 		return;
 	}
+/* it seems some ISPs manage to screw up here, so we have to
+ * relax the error checks... */
+#if 0
 	if ((pkt[5] & 0xfd) != 0xc1) {
 		/* drop scrambled or broken packets */
-		((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
-		((struct dvb_net_priv *) dev->priv)->stats.rx_crc_errors++;
+#else
+	if ((pkt[5] & 0x3c) != 0x00) {
+		/* drop scrambled */
+#endif
+		stats->rx_errors++;
+		stats->rx_crc_errors++;
 		return;
 	}
 	if (pkt[5] & 0x02) {
 		//FIXME: handle LLC/SNAP
-                ((struct dvb_net_priv *)dev->priv)->stats.rx_dropped++;
+                stats->rx_dropped++;
                 return;
         }
 	if (pkt[7]) {
 		/* FIXME: assemble datagram from multiple sections */
-		((struct dvb_net_priv *) dev->priv)->stats.rx_errors++;
-		((struct dvb_net_priv *) dev->priv)->stats.rx_frame_errors++;
+		stats->rx_errors++;
+		stats->rx_frame_errors++;
 		return;
 	}
 
@@ -144,7 +615,7 @@
 	 */
 	if (!(skb = dev_alloc_skb(pkt_len - 4 - 12 + 14 + 2))) {
 		//printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name);
-		((struct dvb_net_priv *) dev->priv)->stats.rx_dropped++;
+		stats->rx_dropped++;
 		return;
 	}
 	skb_reserve(skb, 2);    /* longword align L3 header */
@@ -169,12 +640,12 @@
 
 	skb->protocol = dvb_net_eth_type_trans(skb, dev);
         
-        ((struct dvb_net_priv *)dev->priv)->stats.rx_packets++;
-        ((struct dvb_net_priv *)dev->priv)->stats.rx_bytes+=skb->len;
+	stats->rx_packets++;
+	stats->rx_bytes+=skb->len;
         netif_rx(skb);
 }
  
-static int dvb_net_callback(const u8 *buffer1, size_t buffer1_len,
+static int dvb_net_sec_callback(const u8 *buffer1, size_t buffer1_len,
 		 const u8 *buffer2, size_t buffer2_len,
 		 struct dmx_section_filter *filter,
 		 enum dmx_success success)
@@ -199,7 +670,7 @@
 static u8 mac_allmulti[6]={0x01, 0x00, 0x5e, 0x00, 0x00, 0x00};
 static u8 mask_promisc[6]={0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
-static int dvb_net_filter_set(struct net_device *dev, 
+static int dvb_net_filter_sec_set(struct net_device *dev,
 		   struct dmx_section_filter **secfilter,
 		   u8 *mac, u8 *mac_mask)
 {
@@ -257,10 +728,12 @@
 
 	priv->secfeed=0;
 	priv->secfilter=0;
+	priv->tsfeed = 0;
 
+	if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) {
 	dprintk("%s: alloc secfeed\n", __FUNCTION__);
 	ret=demux->allocate_section_feed(demux, &priv->secfeed, 
-					 dvb_net_callback);
+					 dvb_net_sec_callback);
 	if (ret<0) {
 		printk("%s: could not allocate section feed\n", dev->name);
 		return ret;
@@ -277,41 +750,74 @@
 
 	if (priv->rx_mode != RX_MODE_PROMISC) {
 		dprintk("%s: set secfilter\n", __FUNCTION__);
-		dvb_net_filter_set(dev, &priv->secfilter, mac, mask_normal);
+			dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_normal);
 	}
 
 	switch (priv->rx_mode) {
 	case RX_MODE_MULTI:
 		for (i = 0; i < priv->multi_num; i++) {
 			dprintk("%s: set multi_secfilter[%d]\n", __FUNCTION__, i);
-			dvb_net_filter_set(dev, &priv->multi_secfilter[i],
+				dvb_net_filter_sec_set(dev, &priv->multi_secfilter[i],
 					   priv->multi_macs[i], mask_normal);
 		}
 		break;
 	case RX_MODE_ALL_MULTI:
 		priv->multi_num=1;
 		dprintk("%s: set multi_secfilter[0]\n", __FUNCTION__);
-		dvb_net_filter_set(dev, &priv->multi_secfilter[0],
+			dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0],
 				   mac_allmulti, mask_allmulti);
 		break;
 	case RX_MODE_PROMISC:
 		priv->multi_num=0;
 		dprintk("%s: set secfilter\n", __FUNCTION__);
-		dvb_net_filter_set(dev, &priv->secfilter, mac, mask_promisc);
+			dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc);
 		break;
 	}
 	
 	dprintk("%s: start filtering\n", __FUNCTION__);
 	priv->secfeed->start_filtering(priv->secfeed);
+	} else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) {
+		struct timespec timeout = { 0, 30000000 }; // 30 msec
+
+		/* we have payloads encapsulated in TS */
+		dprintk("%s: alloc tsfeed\n", __FUNCTION__);
+		ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback);
+		if (ret < 0) {
+			printk("%s: could not allocate ts feed\n", dev->name);
+			return ret;
+		}
+
+		/* Set netdevice pointer for ts decaps callback. */
+		priv->tsfeed->priv = (void *)dev;
+		ret = priv->tsfeed->set(priv->tsfeed, priv->pid,
+					TS_PACKET, DMX_TS_PES_OTHER,
+					188 * 100, /* nr. of bytes delivered per callback */
+					32768,     /* circular buffer size */
+					0,         /* descramble */
+					timeout);
+
+		if (ret < 0) {
+			printk("%s: could not set ts feed\n", dev->name);
+			priv->demux->release_ts_feed(priv->demux, priv->tsfeed);
+			priv->tsfeed = 0;
+			return ret;
+		}
+
+		dprintk("%s: start filtering\n", __FUNCTION__);
+		priv->tsfeed->start_filtering(priv->tsfeed);
+	} else
+		return -EINVAL;
+
 	return 0;
 }
 
-static void dvb_net_feed_stop(struct net_device *dev)
+static int dvb_net_feed_stop(struct net_device *dev)
 {
 	struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv;
 	int i;
 
 	dprintk("%s\n", __FUNCTION__);
+	if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) {
         if (priv->secfeed) {
 		if (priv->secfeed->is_filtering) {
 			dprintk("%s: stop secfeed\n", __FUNCTION__);
@@ -327,7 +833,8 @@
 
 		for (i=0; i<priv->multi_num; i++) {
 			if (priv->multi_secfilter[i]) {
-				dprintk("%s: release multi_filter[%d]\n", __FUNCTION__, i);
+					dprintk("%s: release multi_filter[%d]\n",
+						__FUNCTION__, i);
 				priv->secfeed->release_filter(priv->secfeed,
 						       priv->multi_secfilter[i]);
 			priv->multi_secfilter[i]=0;
@@ -338,6 +845,20 @@
 		priv->secfeed=0;
 	} else
 		printk("%s: no feed to stop\n", dev->name);
+	} else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) {
+		if (priv->tsfeed) {
+			if (priv->tsfeed->is_filtering) {
+				dprintk("%s: stop tsfeed\n", __FUNCTION__);
+				priv->tsfeed->stop_filtering(priv->tsfeed);
+			}
+			priv->demux->release_ts_feed(priv->demux, priv->tsfeed);
+			priv->tsfeed = 0;
+		}
+		else
+			printk("%s: no ts feed to stop\n", dev->name);
+	} else
+		return -EINVAL;
+	return 0;
 }
 
 
@@ -446,8 +967,7 @@
 	struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv;
 
 	priv->in_use--;
-        dvb_net_feed_stop(dev);
-	return 0;
+        return dvb_net_feed_stop(dev);
 }
 
 static struct net_device_stats * dvb_net_get_stats(struct net_device *dev)
@@ -489,14 +1007,15 @@
 	return i;
 }
 
-
-static int dvb_net_add_if(struct dvb_net *dvbnet, u16 pid)
+static int dvb_net_add_if(struct dvb_net *dvbnet, u16 pid, u8 feedtype)
 {
         struct net_device *net;
 	struct dvb_net_priv *priv;
 	int result;
 	int if_num;
  
+	if (feedtype != DVB_NET_FEEDTYPE_MPE && feedtype != DVB_NET_FEEDTYPE_ULE)
+		return -EINVAL;
 	if ((if_num = get_if(dvbnet)) < 0)
 		return -EINVAL;
 
@@ -516,6 +1035,10 @@
         priv->demux = dvbnet->demux;
         priv->pid = pid;
 	priv->rx_mode = RX_MODE_UNI;
+	priv->need_pusi = 1;
+	priv->tscc = 0;
+	priv->feedtype = feedtype;
+	reset_ule(priv);
 
 	INIT_WORK(&priv->set_multicast_list_wq, wq_set_multicast_list, net);
 	INIT_WORK(&priv->restart_net_feed_wq, wq_restart_net_feed, net);
@@ -570,7 +1091,7 @@
 		
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
-		result=dvb_net_add_if(dvbnet, dvbnetif->pid);
+		result=dvb_net_add_if(dvbnet, dvbnetif->pid, dvbnetif->feedtype);
 		if (result<0)
 			return result;
 		dvbnetif->if_num=result;
@@ -584,19 +1105,50 @@
 
 		if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX ||
 		    !dvbnet->state[dvbnetif->if_num])
-			return -EFAULT;
+			return -EINVAL;
 
-		netdev=(struct net_device*)&dvbnet->device[dvbnetif->if_num];
+		netdev = dvbnet->device[dvbnetif->if_num];
 		priv_data=(struct dvb_net_priv*)netdev->priv;
 		dvbnetif->pid=priv_data->pid;
+		dvbnetif->feedtype=priv_data->feedtype;
 		break;
 	}
 	case NET_REMOVE_IF:
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
 		return dvb_net_remove_if(dvbnet, (int) (long) parg);
-	default:
+
+	/* binary compatiblity cruft */
+	case __NET_ADD_IF_OLD:
+	{
+		struct __dvb_net_if_old *dvbnetif=(struct __dvb_net_if_old *)parg;
+		int result;
+
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		result=dvb_net_add_if(dvbnet, dvbnetif->pid, DVB_NET_FEEDTYPE_MPE);
+		if (result<0)
+			return result;
+		dvbnetif->if_num=result;
+		break;
+	}
+	case __NET_GET_IF_OLD:
+	{
+		struct net_device *netdev;
+		struct dvb_net_priv *priv_data;
+		struct __dvb_net_if_old *dvbnetif=(struct __dvb_net_if_old *)parg;
+
+		if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX ||
+		    !dvbnet->state[dvbnetif->if_num])
 		return -EINVAL;
+
+		netdev = dvbnet->device[dvbnetif->if_num];
+		priv_data=(struct dvb_net_priv*)netdev->priv;
+		dvbnetif->pid=priv_data->pid;
+		break;
+	}
+	default:
+		return -ENOTTY;
 	}
 	return 0;
 }
diff -urawBN xx-linux-2.6.5/include/linux/dvb/frontend.h linux-2.6.5-patched/include/linux/dvb/frontend.h
--- xx-linux-2.6.5/include/linux/dvb/frontend.h	2003-12-18 03:58:49.000000000 +0100
+++ linux-2.6.5-patched/include/linux/dvb/frontend.h	2004-03-11 19:40:45.000000000 +0100
@@ -59,9 +59,9 @@
 	FE_CAN_BANDWIDTH_AUTO         = 0x40000,
 	FE_CAN_GUARD_INTERVAL_AUTO    = 0x80000,
 	FE_CAN_HIERARCHY_AUTO         = 0x100000,
-	FE_CAN_RECOVER                = 0x20000000,
-	FE_CAN_CLEAN_SETUP            = 0x40000000,
-	FE_CAN_MUTE_TS                = 0x80000000
+	FE_NEEDS_BENDING              = 0x20000000, // frontend requires frequency bending
+	FE_CAN_RECOVER                = 0x40000000, // frontend can recover from a cable unplug automatically
+	FE_CAN_MUTE_TS                = 0x80000000  // frontend can stop spurious TS data output
 } fe_caps_t;
 
 
diff -urawBN xx-linux-2.6.5/include/linux/dvb/net.h linux-2.6.5-patched/include/linux/dvb/net.h
--- xx-linux-2.6.5/include/linux/dvb/net.h	2003-12-18 03:58:08.000000000 +0100
+++ linux-2.6.5-patched/include/linux/dvb/net.h	2004-04-14 19:10:57.000000000 +0200
@@ -30,6 +30,9 @@
 struct dvb_net_if {
 	__u16 pid;
 	__u16 if_num;
+	__u8 feedtype;
+#define DVB_NET_FEEDTYPE_MPE 0	/* multi protocol encapsulation */
+#define DVB_NET_FEEDTYPE_ULE 1	/* ultra lightweight encapsulation */
 };
 
 
@@ -37,5 +40,14 @@
 #define NET_REMOVE_IF              _IO('o', 53)
 #define NET_GET_IF                 _IOWR('o', 54, struct dvb_net_if)
 
-#endif /*_DVBNET_H_*/
 
+/* binary compatibility cruft: */
+struct __dvb_net_if_old {
+	__u16 pid;
+	__u16 if_num;
+};
+#define __NET_ADD_IF_OLD _IOWR('o', 52, struct __dvb_net_if_old)
+#define __NET_GET_IF_OLD _IOWR('o', 54, struct __dvb_net_if_old)
+
+
+#endif /*_DVBNET_H_*/



  reply	other threads:[~2004-04-26 13:57 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2004-04-26 13:40 [PATCH 0/9] LinuxTV.org DVB update Michael Hunold
2004-04-26 13:40 ` [PATCH 1/9] V4L: Update the saa7146 driver Michael Hunold
2004-04-26 13:41   ` [PATCH 2/9] DVB: Documentation and Kconfig updazes Michael Hunold
2004-04-26 13:41     ` [PATCH 3/9] DVB: Update DVB budget drivers Michael Hunold
2004-04-26 13:42       ` [PATCH 4/9] DVB: Add EN50221 cam support to dvb-core Michael Hunold
2004-04-26 13:42         ` Michael Hunold [this message]
2004-04-26 13:42           ` [PATCH 6/9] DVB: AV7110 DVB driver updates Michael Hunold
2004-04-26 13:42             ` [PATCH 7/9] DVB: Misc. DVB frontend " Michael Hunold
2004-04-26 13:42               ` [PATCH 8/9] DVB: Misc. DVB USB " Michael Hunold
2004-04-26 13:42                 ` [PATCH 9/9] DVB: Follow saa7146 changes in affected V4L drivers Michael Hunold
2004-04-26 14:05     ` [PATCH 2/9] DVB: Documentation and Kconfig updazes Måns Rullgård

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=10829868582688@convergence.de \
    --to=hunold@linuxtv.org \
    --cc=akpm@osdl.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=torvalds@osdl.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox