Linux PARISC architecture development
 help / color / mirror / Atom feed
* [parisc-linux] J5000 LED LCD patch (take 2)
@ 2005-03-24 18:48 David Pye
  2005-03-24 20:01 ` [parisc-linux] A second LCD patch David Pye
  2005-03-24 20:05 ` [parisc-linux] J5000 LED LCD patch (take 2) James Bottomley
  0 siblings, 2 replies; 16+ messages in thread
From: David Pye @ 2005-03-24 18:48 UTC (permalink / raw)
  To: parisc-linux


[-- Attachment #1.1.1: Type: text/plain, Size: 1058 bytes --]

Hi,

Attached is the latest iteration of my patch, with the feedback from Grant 
Grundler incorporated (hopefully!)

The other changes are:

Adjust the scheduling so the work queue item runs only every 5 jiffies, 
instead of each one (as per the original tasklet). This means we can do a 
couple more calculations, and still hopefully be ahead in the efficiency 
stakes.

Set and enforce a minimum on-time for LEDs - this prevents the LCD ones 
'ghosting' under rapid LAN/IO on/off. This also applies to LED systems too. 
The minimum on-time is currently 100mS - feedback welcome as to whether this 
should be made LCD-only, or adjusted up/downwards. (This is necessary because 
once the min_cmd_delay has been taken into effect, rapidly fluttering LAN/IO 
lcd symbols cause the heartbeat to stutter slightly).

On my J5000, this gives a perfectly uniform heartbeat regardless of lan/io 
activity, and also prevents the lan/io symbols ghosting during continuous 
traffic.

As before, comments/suggestions welcome!

Cheers,

David

[-- Attachment #1.1.2: led_lcd_patch --]
[-- Type: text/x-diff, Size: 12928 bytes --]

diff -urpN linux-2.6.12-rc1-pa2/arch/parisc/kernel/time.c worktree/arch/parisc/kernel/time.c
--- linux-2.6.12-rc1-pa2/arch/parisc/kernel/time.c	2004-10-21 19:58:24.000000000 +0100
+++ worktree/arch/parisc/kernel/time.c	2005-03-24 01:04:32.000000000 +0000
@@ -89,14 +89,6 @@ irqreturn_t timer_interrupt(int irq, voi
 		}
 	}
     
-#ifdef CONFIG_CHASSIS_LCD_LED
-	/* Only schedule the led tasklet on cpu 0, and only if it
-	 * is enabled.
-	 */
-	if (cpu == 0 && !atomic_read(&led_tasklet.count))
-		tasklet_schedule(&led_tasklet);
-#endif
-
 	/* check soft power switch status */
 	if (cpu == 0 && !atomic_read(&power_tasklet.count))
 		tasklet_schedule(&power_tasklet);
diff -urpN linux-2.6.12-rc1-pa2/drivers/parisc/led.c worktree/drivers/parisc/led.c
--- linux-2.6.12-rc1-pa2/drivers/parisc/led.c	2005-03-18 13:17:10.000000000 +0000
+++ worktree/drivers/parisc/led.c	2005-03-24 01:07:08.000000000 +0000
@@ -18,6 +18,9 @@
  * Changes:
  *      - Audit copy_from_user in led_proc_write.
  *                                Daniele Bellucci <bellucda@tiscali.it>
+ *	- Switch from using a tasklet to a work queue, so the led_LCD_driver
+ *	  	can sleep.
+ *	  			  David Pye <dmp@davidmpye.dyndns.org>
  */
 
 #include <linux/config.h>
@@ -37,6 +40,7 @@
 #include <linux/proc_fs.h>
 #include <linux/ctype.h>
 #include <linux/blkdev.h>
+#include <linux/workqueue.h>
 #include <asm/io.h>
 #include <asm/processor.h>
 #include <asm/hardware.h>
@@ -46,9 +50,9 @@
 #include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
-   completely in software. The necessary calculations are done in a tasklet
-   which is scheduled at every timer interrupt and since the calculations 
-   may consume relatively much CPU-time some of the calculations can be 
+   completely in software. The necessary calculations are done in a work queue
+   task which is scheduled regularly, and since the calculations may consume a 
+   relatively large amount of CPU time, some of the calculations can be 
    turned off with the following variables (controlled via procfs) */
 
 static int led_type = -1;
@@ -58,13 +62,16 @@ static int led_lanrxtx = 1;
 static char lcd_text[32];
 static char lcd_text_default[32];
 
+static struct workqueue_struct *led_wq;
+static void led_work_func(void *);
+static DECLARE_WORK(led_task, led_work_func, NULL);
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
 #define DPRINTK(x)
 #endif
 
-
 struct lcd_block {
 	unsigned char command;	/* stores the command byte      */
 	unsigned char on;	/* value for turning LED on     */
@@ -115,12 +122,24 @@ lcd_info __attribute__((aligned(8))) =
 #define LCD_DATA_REG	lcd_info.lcd_data_reg_addr	 
 #define LED_DATA_REG	lcd_info.lcd_cmd_reg_addr	/* LASI & ASP only */
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+
+/* The workqueue must be created at init-time */
+static int start_task(void) 
+{	
+	/* Create the work queue and queue the LED task */
+	led_wq = create_singlethread_workqueue("led_wq");	
+	queue_work(led_wq, &led_task);
+
+	return 0;
+}
+
+device_initcall(start_task);
 
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
-#define LED_HASLCD 1
-#define LED_NOLCD  0
 #ifdef CONFIG_PROC_FS
 static int led_proc_read(char *page, char **start, off_t off, int count, 
 	int *eof, void *data)
@@ -285,53 +304,41 @@ static void led_LASI_driver(unsigned cha
 /*
    ** 
    ** led_LCD_driver()
-   ** 
-   ** The logic of the LCD driver is, that we write at every scheduled call
-   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
-   ** That way we don't need to let this tasklet busywait for min_cmd_delay
-   ** milliseconds.
-   **
-   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
    **   
  */
 static void led_LCD_driver(unsigned char leds)
 {
-	static int last_index;	/* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
-	static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
-	struct lcd_block *block_ptr;
-	int value;
-
-	switch (last_index) {
-	    case 0:	block_ptr = &lcd_info.heartbeat;
-			value = leds & LED_HEARTBEAT;
-			break;
-	    case 1:	block_ptr = &lcd_info.disk_io;
-			value = leds & LED_DISK_IO;
-			break;					
-	    case 2:	block_ptr = &lcd_info.lan_rcv;
-			value = leds & LED_LAN_RCV;
-			break;					
-	    case 3:	block_ptr = &lcd_info.lan_tx;
-			value = leds & LED_LAN_TX;
-			break;
-	    default:	/* should never happen: */
-			return;
-	}
+	static int lastleds, i;
+	static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
+		LED_LAN_RCV, LED_LAN_TX };
+	
+	static struct lcd_block * blockp[4] = {
+		&lcd_info.heartbeat,
+		&lcd_info.disk_io,
+		&lcd_info.lan_rcv,
+		&lcd_info.lan_tx
+	};
 
-	if (last_was_cmd) {
-	    /* write the value to the LCD data port */
-    	    gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
-	} else {
-	    /* write the command-byte to the LCD command register */
-    	    gsc_writeb( block_ptr->command, LCD_CMD_REG );
-	}    
-	
-	/* now update the vars for the next interrupt iteration */ 
-	if (++last_was_cmd == 2) { /* switch between cmd & data */
-	    last_was_cmd = 0;
-	    if (++last_index == 4) 
-		last_index = 0;	 /* switch back to heartbeat index */
+	/* Convert min_cmd_delay (uS) to jiffies */
+	unsigned long wait_jiffies = 1 + (lcd_info.min_cmd_delay * HZ) / 1000000;
+
+	for (i=0; i<4; ++i) 
+	{
+		if ((leds & mask[i]) != (lastleds & mask[i])) 
+		{
+			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
+			
+			set_current_state(TASK_UNINTERRUPTIBLE);		
+			schedule_timeout(wait_jiffies);	
+
+			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
+					blockp[i]->off, LCD_DATA_REG );
+			
+			set_current_state(TASK_UNINTERRUPTIBLE);		
+			schedule_timeout(wait_jiffies);	
+		}
 	}
+	lastleds = leds;
 }
 
 
@@ -355,7 +362,7 @@ static __inline__ int led_get_net_activi
 
 	rx_total = tx_total = 0;
 	
-	/* we are running as tasklet, so locking dev_base 
+	/* we are running as a workqueue task, so locking dev_base 
 	 * for reading should be OK */
 	read_lock(&dev_base_lock);
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -417,10 +424,9 @@ static __inline__ int led_get_diskio_act
 
 
 /*
-   ** led_tasklet_func()
+   ** led_work_func()
    ** 
-   ** is scheduled at every timer interrupt from time.c and
-   ** updates the chassis LCD/LED 
+   ** is scheduled every 5 jiffies and updates the chassis LCD/LED 
 
     TODO:
     - display load average (older machines like 715/64 have 4 "free" LED's for that)
@@ -431,66 +437,102 @@ static __inline__ int led_get_diskio_act
 #define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
 #define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 
-#define NORMALIZED_COUNT(count) (count/(HZ/100))
+#define LED_MINIMUM_ON_TIME (HZ*10/100)
 
-static void led_tasklet_func(unsigned long unused)
+static void led_work_func (void *unused)
 {
 	static unsigned char lastleds;
-	unsigned char currentleds; /* stores current value of the LEDs */
-	static unsigned long count; /* static incremented value, not wrapped */
 	static unsigned long count_HZ; /* counter in range 0..HZ */
 
+	static unsigned long diskio_on_jiffies;
+	static unsigned long lantx_on_jiffies;
+	static unsigned long lanrcv_on_jiffies;
+
+	unsigned char currentleds = 0; /* stores current value of the LEDs */
+	
 	/* exit if not initialized */
 	if (!led_func_ptr)
 	    return;
 
-	/* increment the local counters */
-	++count;
-	if (++count_HZ == HZ)
+	/* increment the local counter */
+	count_HZ += 5;
+	if (count_HZ == HZ)
 	    count_HZ = 0;
 
-	currentleds = lastleds;
-
 	if (led_heartbeat)
 	{
 		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
 		if (count_HZ<HEARTBEAT_LEN || 
 		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
 		    currentleds |= LED_HEARTBEAT;
-		else
-		    currentleds &= ~LED_HEARTBEAT;
 	}
 
-	/* look for network activity and flash LEDs respectively */
-	if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0)
+	/* If the leds are on, but should be turned off, make sure they have been on for
+	 * the minimum time, to prevent 'flutter' 
+	 */
+
+	/* look for network activity and flash LEDs accordingly */
+	if (led_lanrxtx)
 	{
-		currentleds &= ~(LED_LAN_RCV | LED_LAN_TX);
-		currentleds |= led_get_net_activity();
+		unsigned char new_lan_leds = led_get_net_activity();
+	
+		/* TX LED */
+		if (new_lan_leds & LED_LAN_TX) 
+		{
+			currentleds |= LED_LAN_TX;
+			lantx_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_LAN_TX && time_before(jiffies, lantx_on_jiffies + LED_MINIMUM_ON_TIME))
+		{
+			currentleds |= LED_LAN_TX;
+		}
+		
+		/* RCV LED */
+
+		if (new_lan_leds & LED_LAN_RCV) 
+		{
+			currentleds |= LED_LAN_RCV;
+			lanrcv_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_LAN_RCV && time_before(jiffies, lanrcv_on_jiffies + LED_MINIMUM_ON_TIME)) 
+		{
+			currentleds |= LED_LAN_RCV;
+		}
 	}
 
-	/* avoid to calculate diskio-stats at same irq  as netio-stats */
-	if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0)
+	/* Calculate disk IO */
+	if (led_diskio)
 	{
-		currentleds &= ~LED_DISK_IO;
-		currentleds |= led_get_diskio_activity();
+		unsigned char new_io_led = led_get_diskio_activity();
+		
+		if (new_io_led & LED_DISK_IO) 
+		{
+			currentleds |= LED_DISK_IO;
+			diskio_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_DISK_IO && time_before(jiffies, diskio_on_jiffies + LED_MINIMUM_ON_TIME)) 
+		{
+			currentleds |= LED_DISK_IO;
+		}
 	}
 
 	/* blink all LEDs twice a second if we got an Oops (HPMC) */
-	if (oops_in_progress) {
+	if (oops_in_progress) 
+	{
 		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
 	}
 	
 	/* update the LCD/LEDs */
-	if (currentleds != lastleds) {
-	    led_func_ptr(currentleds);
-	    lastleds = currentleds;
+	if (currentleds != lastleds)
+	{
+		led_func_ptr(currentleds);
+		lastleds = currentleds;
 	}
+	
+	/* Requeue ourself to run in 5 jiffies */	
+	queue_delayed_work(led_wq, &led_task, 5);
 }
 
-/* main led tasklet struct (scheduled from time.c) */
-DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
-
-
 /*
    ** led_halt()
    ** 
@@ -519,9 +561,13 @@ static int led_halt(struct notifier_bloc
 	default:		return NOTIFY_DONE;
 	}
 	
-	/* completely stop the LED/LCD tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* Cancel the work item and delete the queue */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+		destroy_workqueue(led_wq);
+	}
+ 
 	if (lcd_info.model == DISPLAY_MODEL_LCD)
 		lcd_print(txt);
 	else
@@ -586,9 +632,6 @@ int __init register_led_driver(int model
 	initialized++;
 	register_reboot_notifier(&led_notifier);
 
-	/* start the led tasklet for the first time */
-	tasklet_enable(&led_tasklet);
-	
 	return 0;
 }
 
@@ -623,7 +666,7 @@ void __init register_led_regions(void)
    ** lcd_print()
    ** 
    ** Displays the given string on the LCD-Display of newer machines.
-   ** lcd_print() disables the timer-based led tasklet during its 
+   ** lcd_print() disables the timer-based led work task during its 
    ** execution and enables it afterwards again.
    **
  */
@@ -634,8 +677,13 @@ int lcd_print( char *str )
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
-	/* temporarily disable the led tasklet */
-	tasklet_disable(&led_tasklet);
+	/* temporarily disable the led work task */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+	}
+ 	/* copy display string to buffer for procfs */
+ 	strlcpy(lcd_text, str, sizeof(lcd_text));
 
 	/* copy display string to buffer for procfs */
 	strlcpy(lcd_text, str, sizeof(lcd_text));
@@ -653,8 +701,10 @@ int lcd_print( char *str )
 	    udelay(lcd_info.min_cmd_delay);
 	}
 	
-	/* re-enable the led tasklet */
-	tasklet_enable(&led_tasklet);
+	/* re-queue the work */
+	if (led_wq) {
+		queue_work(led_wq, &led_task);
+	}
 
 	return lcd_info.lcd_width;
 }
diff -urpN linux-2.6.12-rc1-pa2/include/asm-parisc/led.h worktree/include/asm-parisc/led.h
--- linux-2.6.12-rc1-pa2/include/asm-parisc/led.h	2004-12-30 08:07:48.000000000 +0000
+++ worktree/include/asm-parisc/led.h	2005-03-24 01:04:54.000000000 +0000
@@ -23,9 +23,6 @@
 
 #define LED_CMD_REG_NONE 0		/* NULL == no addr for the cmd register */
 
-/* led tasklet struct */
-extern struct tasklet_struct led_tasklet;
-
 /* register_led_driver() */
 int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
 

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [parisc-linux] A second LCD patch
  2005-03-24 18:48 [parisc-linux] J5000 LED LCD patch (take 2) David Pye
@ 2005-03-24 20:01 ` David Pye
  2005-03-24 20:05 ` [parisc-linux] J5000 LED LCD patch (take 2) James Bottomley
  1 sibling, 0 replies; 16+ messages in thread
From: David Pye @ 2005-03-24 20:01 UTC (permalink / raw)
  To: parisc-linux


[-- Attachment #1.1.1: Type: text/plain, Size: 470 bytes --]

Hi,

Attached is another patch to the LED LCD code, which prints the initial 
'default lcd text' to the display slightly later than previously.

This avoids it being overwritten by the chassis code which is displayed on my 
J5000 shortly after kernel init (INI CC01). The device_init function created 
for the workqueue is an ideal place to do the initial lcd_print now, afaics.

My previous patch needs to be applied first, then this one.

Thanks,

David

[-- Attachment #1.1.2: led_lcd_patch2 --]
[-- Type: text/x-diff, Size: 964 bytes --]

diff -urpN oldtree/drivers/parisc/led.c newtree/drivers/parisc/led.c
--- oldtree/drivers/parisc/led.c	2005-03-24 19:17:25.000000000 +0000
+++ newtree/drivers/parisc/led.c	2005-03-24 19:19:07.000000000 +0000
@@ -128,6 +128,10 @@ lcd_info __attribute__((aligned(8))) =
 /* The workqueue must be created at init-time */
 static int start_task(void) 
 {	
+	/* Print the display text to the lcd if present now. This avoids it
+	 * being overwritten with any boot-time chassis codes */
+	if (led_type == LED_HASLCD) lcd_print( lcd_text_default );
+
 	/* Create the work queue and queue the LED task */
 	led_wq = create_singlethread_workqueue("led_wq");	
 	queue_work(led_wq, &led_task);
@@ -602,7 +606,6 @@ int __init register_led_driver(int model
 		printk(KERN_INFO "LCD display at %lx,%lx registered\n", 
 			LCD_CMD_REG , LCD_DATA_REG);
 		led_func_ptr = led_LCD_driver;
-		lcd_print( lcd_text_default );
 		led_type = LED_HASLCD;
 		break;
 

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-24 18:48 [parisc-linux] J5000 LED LCD patch (take 2) David Pye
  2005-03-24 20:01 ` [parisc-linux] A second LCD patch David Pye
@ 2005-03-24 20:05 ` James Bottomley
  2005-03-24 21:00   ` David Pye
  1 sibling, 1 reply; 16+ messages in thread
From: James Bottomley @ 2005-03-24 20:05 UTC (permalink / raw)
  To: dmp; +Cc: PARISC list

On Thu, 2005-03-24 at 18:48 +0000, David Pye wrote:
> +                       set_current_state(TASK_UNINTERRUPTIBLE);                
> +                       schedule_timeout(wait_jiffies); 

The two uses of this should become msleep().  schedule_timeout() is
deprecated in the kernel now.

> +       /* Requeue ourself to run in 5 jiffies */       
> +       queue_delayed_work(led_wq, &led_task, 5);

This is wrong too.  The value of HZ (the rate at which jiffies tick) has
been tampered with several times over the course of the years.  All
timing related quantites need to be expressed in terms of HZ to ensure
they remain constant.

We actually have a class of machines for whom HZ=100 so you'll be
waiting ten times longer than you expect on these...


> +               if (cancel_delayed_work(&led_task) == 0) 
> +                       flush_workqueue(led_wq);

This should really be done using 

cancel_rearming_delayed_workqueue()

Unfortunately, someone needs to get that symbol exported.

This added strlcpy is redundant:


> +       /* copy display string to buffer for procfs */
> +       strlcpy(lcd_text, str, sizeof(lcd_text));
>  
>         /* copy display string to buffer for procfs */
>         strlcpy(lcd_text, str, sizeof(lcd_text));
> 

James



_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-24 20:05 ` [parisc-linux] J5000 LED LCD patch (take 2) James Bottomley
@ 2005-03-24 21:00   ` David Pye
  2005-03-25  6:25     ` Grant Grundler
  0 siblings, 1 reply; 16+ messages in thread
From: David Pye @ 2005-03-24 21:00 UTC (permalink / raw)
  To: James Bottomley; +Cc: PARISC list


[-- Attachment #1.1.1: Type: text/plain, Size: 431 bytes --]

Hi,

Thanks for your feedback.

I wish the kernel driver development book clearly marked what was deprecated 
these days </whinge>

I believe the attached patch fixes the points you noted, with the exception of 
not yet using cancel_rearming_delayed_workqueue().   

The second patch I submitted still applies okay after this one. (offset -2 
lines thanks to the removal of the duplicated strlcpy).

Thanks,

David

[-- Attachment #1.1.2: led_lcd_patch --]
[-- Type: text/x-diff, Size: 12843 bytes --]

diff -urpN oldtree/arch/parisc/kernel/time.c newtree/arch/parisc/kernel/time.c
--- oldtree/arch/parisc/kernel/time.c	2004-10-21 19:58:24.000000000 +0100
+++ newtree/arch/parisc/kernel/time.c	2005-03-24 20:31:59.000000000 +0000
@@ -89,14 +89,6 @@ irqreturn_t timer_interrupt(int irq, voi
 		}
 	}
     
-#ifdef CONFIG_CHASSIS_LCD_LED
-	/* Only schedule the led tasklet on cpu 0, and only if it
-	 * is enabled.
-	 */
-	if (cpu == 0 && !atomic_read(&led_tasklet.count))
-		tasklet_schedule(&led_tasklet);
-#endif
-
 	/* check soft power switch status */
 	if (cpu == 0 && !atomic_read(&power_tasklet.count))
 		tasklet_schedule(&power_tasklet);
diff -urpN oldtree/drivers/parisc/led.c newtree/drivers/parisc/led.c
--- oldtree/drivers/parisc/led.c	2005-03-18 13:17:10.000000000 +0000
+++ newtree/drivers/parisc/led.c	2005-03-24 20:32:49.000000000 +0000
@@ -18,6 +18,9 @@
  * Changes:
  *      - Audit copy_from_user in led_proc_write.
  *                                Daniele Bellucci <bellucda@tiscali.it>
+ *	- Switch from using a tasklet to a work queue, so the led_LCD_driver
+ *	  	can sleep.
+ *	  			  David Pye <dmp@davidmpye.dyndns.org>
  */
 
 #include <linux/config.h>
@@ -37,6 +40,7 @@
 #include <linux/proc_fs.h>
 #include <linux/ctype.h>
 #include <linux/blkdev.h>
+#include <linux/workqueue.h>
 #include <asm/io.h>
 #include <asm/processor.h>
 #include <asm/hardware.h>
@@ -46,9 +50,9 @@
 #include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
-   completely in software. The necessary calculations are done in a tasklet
-   which is scheduled at every timer interrupt and since the calculations 
-   may consume relatively much CPU-time some of the calculations can be 
+   completely in software. The necessary calculations are done in a work queue
+   task which is scheduled regularly, and since the calculations may consume a 
+   relatively large amount of CPU time, some of the calculations can be 
    turned off with the following variables (controlled via procfs) */
 
 static int led_type = -1;
@@ -58,13 +62,16 @@ static int led_lanrxtx = 1;
 static char lcd_text[32];
 static char lcd_text_default[32];
 
+static struct workqueue_struct *led_wq;
+static void led_work_func(void *);
+static DECLARE_WORK(led_task, led_work_func, NULL);
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
 #define DPRINTK(x)
 #endif
 
-
 struct lcd_block {
 	unsigned char command;	/* stores the command byte      */
 	unsigned char on;	/* value for turning LED on     */
@@ -115,12 +122,24 @@ lcd_info __attribute__((aligned(8))) =
 #define LCD_DATA_REG	lcd_info.lcd_data_reg_addr	 
 #define LED_DATA_REG	lcd_info.lcd_cmd_reg_addr	/* LASI & ASP only */
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+
+/* The workqueue must be created at init-time */
+static int start_task(void) 
+{	
+	/* Create the work queue and queue the LED task */
+	led_wq = create_singlethread_workqueue("led_wq");	
+	queue_work(led_wq, &led_task);
+
+	return 0;
+}
+
+device_initcall(start_task);
 
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
-#define LED_HASLCD 1
-#define LED_NOLCD  0
 #ifdef CONFIG_PROC_FS
 static int led_proc_read(char *page, char **start, off_t off, int count, 
 	int *eof, void *data)
@@ -285,53 +304,37 @@ static void led_LASI_driver(unsigned cha
 /*
    ** 
    ** led_LCD_driver()
-   ** 
-   ** The logic of the LCD driver is, that we write at every scheduled call
-   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
-   ** That way we don't need to let this tasklet busywait for min_cmd_delay
-   ** milliseconds.
-   **
-   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
    **   
  */
 static void led_LCD_driver(unsigned char leds)
 {
-	static int last_index;	/* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
-	static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
-	struct lcd_block *block_ptr;
-	int value;
-
-	switch (last_index) {
-	    case 0:	block_ptr = &lcd_info.heartbeat;
-			value = leds & LED_HEARTBEAT;
-			break;
-	    case 1:	block_ptr = &lcd_info.disk_io;
-			value = leds & LED_DISK_IO;
-			break;					
-	    case 2:	block_ptr = &lcd_info.lan_rcv;
-			value = leds & LED_LAN_RCV;
-			break;					
-	    case 3:	block_ptr = &lcd_info.lan_tx;
-			value = leds & LED_LAN_TX;
-			break;
-	    default:	/* should never happen: */
-			return;
-	}
+	static int lastleds, i;
+	static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
+		LED_LAN_RCV, LED_LAN_TX };
+	
+	static struct lcd_block * blockp[4] = {
+		&lcd_info.heartbeat,
+		&lcd_info.disk_io,
+		&lcd_info.lan_rcv,
+		&lcd_info.lan_tx
+	};
 
-	if (last_was_cmd) {
-	    /* write the value to the LCD data port */
-    	    gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
-	} else {
-	    /* write the command-byte to the LCD command register */
-    	    gsc_writeb( block_ptr->command, LCD_CMD_REG );
-	}    
-	
-	/* now update the vars for the next interrupt iteration */ 
-	if (++last_was_cmd == 2) { /* switch between cmd & data */
-	    last_was_cmd = 0;
-	    if (++last_index == 4) 
-		last_index = 0;	 /* switch back to heartbeat index */
+	/* Convert min_cmd_delay to milliseconds */
+	unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000);
+	
+	for (i=0; i<4; ++i) 
+	{
+		if ((leds & mask[i]) != (lastleds & mask[i])) 
+		{
+			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
+			msleep(msec_cmd_delay);
+			
+			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
+					blockp[i]->off, LCD_DATA_REG );
+			msleep(msec_cmd_delay);
+		}
 	}
+	lastleds = leds;
 }
 
 
@@ -355,7 +358,7 @@ static __inline__ int led_get_net_activi
 
 	rx_total = tx_total = 0;
 	
-	/* we are running as tasklet, so locking dev_base 
+	/* we are running as a workqueue task, so locking dev_base 
 	 * for reading should be OK */
 	read_lock(&dev_base_lock);
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -417,10 +420,9 @@ static __inline__ int led_get_diskio_act
 
 
 /*
-   ** led_tasklet_func()
+   ** led_work_func()
    ** 
-   ** is scheduled at every timer interrupt from time.c and
-   ** updates the chassis LCD/LED 
+   ** is scheduled every 5 milliseconds and updates the chassis LCD/LED 
 
     TODO:
     - display load average (older machines like 715/64 have 4 "free" LED's for that)
@@ -431,66 +433,104 @@ static __inline__ int led_get_diskio_act
 #define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
 #define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 
-#define NORMALIZED_COUNT(count) (count/(HZ/100))
+#define LED_MINIMUM_ON_TIME (HZ*10/100)
 
-static void led_tasklet_func(unsigned long unused)
+#define LED_UPDATE_INTERVAL (1 + (HZ*4/1000))
+
+static void led_work_func (void *unused)
 {
 	static unsigned char lastleds;
-	unsigned char currentleds; /* stores current value of the LEDs */
-	static unsigned long count; /* static incremented value, not wrapped */
 	static unsigned long count_HZ; /* counter in range 0..HZ */
 
+	static unsigned long diskio_on_jiffies;
+	static unsigned long lantx_on_jiffies;
+	static unsigned long lanrcv_on_jiffies;
+
+	unsigned char currentleds = 0; /* stores current value of the LEDs */
+	
 	/* exit if not initialized */
 	if (!led_func_ptr)
 	    return;
 
-	/* increment the local counters */
-	++count;
-	if (++count_HZ == HZ)
+	/* increment the local counter */
+	count_HZ += LED_UPDATE_INTERVAL;
+	if (count_HZ >= HZ)
 	    count_HZ = 0;
 
-	currentleds = lastleds;
-
 	if (led_heartbeat)
 	{
 		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
 		if (count_HZ<HEARTBEAT_LEN || 
 		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
 		    currentleds |= LED_HEARTBEAT;
-		else
-		    currentleds &= ~LED_HEARTBEAT;
 	}
 
-	/* look for network activity and flash LEDs respectively */
-	if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0)
+	/* If the leds are on, but should be turned off, make sure they have been on for
+	 * the minimum time, to prevent 'flutter' 
+	 */
+
+	/* look for network activity and flash LEDs accordingly */
+	if (led_lanrxtx)
 	{
-		currentleds &= ~(LED_LAN_RCV | LED_LAN_TX);
-		currentleds |= led_get_net_activity();
+		unsigned char new_lan_leds = led_get_net_activity();
+	
+		/* TX LED */
+		if (new_lan_leds & LED_LAN_TX) 
+		{
+			currentleds |= LED_LAN_TX;
+			lantx_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_LAN_TX && time_before(jiffies, lantx_on_jiffies + LED_MINIMUM_ON_TIME))
+		{
+			currentleds |= LED_LAN_TX;
+		}
+		
+		/* RCV LED */
+
+		if (new_lan_leds & LED_LAN_RCV) 
+		{
+			currentleds |= LED_LAN_RCV;
+			lanrcv_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_LAN_RCV && time_before(jiffies, lanrcv_on_jiffies + LED_MINIMUM_ON_TIME)) 
+		{
+			currentleds |= LED_LAN_RCV;
+		}
 	}
 
-	/* avoid to calculate diskio-stats at same irq  as netio-stats */
-	if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0)
+	/* Calculate disk IO */
+	if (led_diskio)
 	{
-		currentleds &= ~LED_DISK_IO;
-		currentleds |= led_get_diskio_activity();
+		unsigned char new_io_led = led_get_diskio_activity();
+		
+		if (new_io_led & LED_DISK_IO) 
+		{
+			currentleds |= LED_DISK_IO;
+			diskio_on_jiffies = jiffies;
+		}
+		else if (lastleds & LED_DISK_IO && time_before(jiffies, diskio_on_jiffies + LED_MINIMUM_ON_TIME)) 
+		{
+			currentleds |= LED_DISK_IO;
+		}
 	}
 
 	/* blink all LEDs twice a second if we got an Oops (HPMC) */
-	if (oops_in_progress) {
+	if (oops_in_progress) 
+	{
 		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
 	}
 	
 	/* update the LCD/LEDs */
-	if (currentleds != lastleds) {
-	    led_func_ptr(currentleds);
-	    lastleds = currentleds;
+	if (currentleds != lastleds)
+	{
+		led_func_ptr(currentleds);
+		lastleds = currentleds;
 	}
+	
+	/* Requeue ourself to run in 5 jiffies */	
+	queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL);
 }
 
-/* main led tasklet struct (scheduled from time.c) */
-DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
-
-
 /*
    ** led_halt()
    ** 
@@ -519,9 +559,13 @@ static int led_halt(struct notifier_bloc
 	default:		return NOTIFY_DONE;
 	}
 	
-	/* completely stop the LED/LCD tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* Cancel the work item and delete the queue */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+		destroy_workqueue(led_wq);
+	}
+ 
 	if (lcd_info.model == DISPLAY_MODEL_LCD)
 		lcd_print(txt);
 	else
@@ -586,9 +630,6 @@ int __init register_led_driver(int model
 	initialized++;
 	register_reboot_notifier(&led_notifier);
 
-	/* start the led tasklet for the first time */
-	tasklet_enable(&led_tasklet);
-	
 	return 0;
 }
 
@@ -623,7 +664,7 @@ void __init register_led_regions(void)
    ** lcd_print()
    ** 
    ** Displays the given string on the LCD-Display of newer machines.
-   ** lcd_print() disables the timer-based led tasklet during its 
+   ** lcd_print() disables the timer-based led work task during its 
    ** execution and enables it afterwards again.
    **
  */
@@ -634,12 +675,14 @@ int lcd_print( char *str )
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
-	/* temporarily disable the led tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* temporarily disable the led work task */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+	}
 	/* copy display string to buffer for procfs */
 	strlcpy(lcd_text, str, sizeof(lcd_text));
-	
+
 	/* Set LCD Cursor to 1st character */
 	gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
 	udelay(lcd_info.min_cmd_delay);
@@ -653,8 +696,10 @@ int lcd_print( char *str )
 	    udelay(lcd_info.min_cmd_delay);
 	}
 	
-	/* re-enable the led tasklet */
-	tasklet_enable(&led_tasklet);
+	/* re-queue the work */
+	if (led_wq) {
+		queue_work(led_wq, &led_task);
+	}
 
 	return lcd_info.lcd_width;
 }
diff -urpN oldtree/include/asm-parisc/led.h newtree/include/asm-parisc/led.h
--- oldtree/include/asm-parisc/led.h	2004-12-30 08:07:48.000000000 +0000
+++ newtree/include/asm-parisc/led.h	2005-03-24 20:31:59.000000000 +0000
@@ -23,9 +23,6 @@
 
 #define LED_CMD_REG_NONE 0		/* NULL == no addr for the cmd register */
 
-/* led tasklet struct */
-extern struct tasklet_struct led_tasklet;
-
 /* register_led_driver() */
 int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
 

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-24 21:00   ` David Pye
@ 2005-03-25  6:25     ` Grant Grundler
  2005-03-25 17:05       ` David Pye
  0 siblings, 1 reply; 16+ messages in thread
From: Grant Grundler @ 2005-03-25  6:25 UTC (permalink / raw)
  To: David Pye; +Cc: James Bottomley, PARISC list

On Thu, Mar 24, 2005 at 09:00:05PM +0000, David Pye wrote:
...
> +	for (i=0; i<4; ++i) 
> +	{
> +		if ((leds & mask[i]) != (lastleds & mask[i])) 
> +		{
> +			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
> +			msleep(msec_cmd_delay);
> +			
> +			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
> +					blockp[i]->off, LCD_DATA_REG );
> +			msleep(msec_cmd_delay);
> +		}
>  	}
> +	lastleds = leds;

I still *really* like this bit. Good work!

> -   ** updates the chassis LCD/LED 
> +   ** is scheduled every 5 milliseconds and updates the chassis LCD/LED 
...
> -#define NORMALIZED_COUNT(count) (count/(HZ/100))
> +#define LED_MINIMUM_ON_TIME (HZ*10/100)

Why not just update every 10ms and ditch all the "minimum on time" stuff?
Can someone tell if an LED/LCD came on 5ms later than it should have been?

> +	static unsigned long diskio_on_jiffies;
> +	static unsigned long lantx_on_jiffies;
> +	static unsigned long lanrcv_on_jiffies;

If not, then this bit and...

> +	/* If the leds are on, but should be turned off, make sure they have been on for
> +	 * the minimum time, to prevent 'flutter' 
> +	 */

...and...

> +		else if (lastleds & LED_LAN_TX && time_before(jiffies, lantx_on_jiffies + LED_MINIMUM_ON_TIME))
> +		{
> +			currentleds |= LED_LAN_TX;
> +		}

can go away. Ditto for DiskIO.

We already know heartbeat stays on/off longer than that (about).

>  	/* blink all LEDs twice a second if we got an Oops (HPMC) */
> -	if (oops_in_progress) {
> +	if (oops_in_progress) 
> +	{
>  		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
>  	}

Will this work for LCD?
Will the itimer continue to run and workq continue to get serviced?
Even if it does, I'm nervous about it.
Maybe stop all activity at that point?

The panic chassis log will get printed and that should be a good indicator.
Anyway, this is a seperate issue from the rest of the patch.

thanks,
grant
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-25  6:25     ` Grant Grundler
@ 2005-03-25 17:05       ` David Pye
  2005-03-25 21:50         ` David Pye
  0 siblings, 1 reply; 16+ messages in thread
From: David Pye @ 2005-03-25 17:05 UTC (permalink / raw)
  To: Grant Grundler; +Cc: parisc-linux


[-- Attachment #1.1: Type: text/plain, Size: 2446 bytes --]

On Friday 25 March 2005 06:25, you wrote:
> On Thu, Mar 24, 2005 at 09:00:05PM +0000, David Pye wrote:
> ...
>
> > + for (i=0; i<4; ++i)
> > + {
> > +  if ((leds & mask[i]) != (lastleds & mask[i]))
> > +  {
> > +   gsc_writeb( blockp[i]->command, LCD_CMD_REG );
> > +   msleep(msec_cmd_delay);
> > +
> > +   gsc_writeb( leds & mask[i] ? blockp[i]->on :
> > +     blockp[i]->off, LCD_DATA_REG );
> > +   msleep(msec_cmd_delay);
> > +  }
> >   }
> > + lastleds = leds;
>
> I still *really* like this bit. Good work!

Thankyou :)

> > -   ** updates the chassis LCD/LED
> > +   ** is scheduled every 5 milliseconds and updates the chassis LCD/LED
>
> ...
>
> > -#define NORMALIZED_COUNT(count) (count/(HZ/100))
> > +#define LED_MINIMUM_ON_TIME (HZ*10/100)
>
> Why not just update every 10ms and ditch all the "minimum on time" stuff?
> Can someone tell if an LED/LCD came on 5ms later than it should have been?

There is a reason for this, though there may be a better way of doing it.  

If you remove it, and set the update interval to 10mS, what tends to happen is 
that because each time you flip a symbol on the lcd you have to wait 2 
jiffies (once before cmd reg, once before writing data reg), and sometimes 
more, because the kernel may put you to sleep a bit longer than the time you 
asked for,  is that the heartbeat starts skipping beats when the io/lan leds 
are changing on/off all the time.

If you really don't like the minimum_on_time, another solution is to leave the 
work queued every five secs, and make sure you service the heartbeat on one 
cycle, and the led/diskio on the other.  If you think that's cleaner, I can 
change it to do that.

> >   /* blink all LEDs twice a second if we got an Oops (HPMC) */
> > - if (oops_in_progress) {
> > + if (oops_in_progress)
> > + {
> >    currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
> >   }
>
> Will this work for LCD?
> Will the itimer continue to run and workq continue to get serviced?
> Even if it does, I'm nervous about it.
> Maybe stop all activity at that point?
> The panic chassis log will get printed and that should be a good indicator.
> Anyway, this is a seperate issue from the rest of the patch.

I think it'd be better to just rewrite that so that the work only requeues 
itself if an oops ISNT in progress, but as you say, it wasn't something I 
changed!

Cheers,

David

>
> thanks,
> grant

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-25 17:05       ` David Pye
@ 2005-03-25 21:50         ` David Pye
  2005-03-26  7:13           ` Grant Grundler
  0 siblings, 1 reply; 16+ messages in thread
From: David Pye @ 2005-03-25 21:50 UTC (permalink / raw)
  To: Grant Grundler; +Cc: parisc-linux


[-- Attachment #1.1: Type: text/plain, Size: 1733 bytes --]

On Friday 25 March 2005 17:05, David Pye wrote:
<snip>
> >
> > > -#define NORMALIZED_COUNT(count) (count/(HZ/100))
> > > +#define LED_MINIMUM_ON_TIME (HZ*10/100)
> >
> > Why not just update every 10ms and ditch all the "minimum on time" stuff?
> > Can someone tell if an LED/LCD came on 5ms later than it should have
> > been?

Ah, I remember now.

That's 100mS, not 10mS for a start ;)  You can't make the whole update 
interval 100mS, of course, or you'll miss the heartbeats completely.

> There is a reason for this, though there may be a better way of doing it.
>
> If you remove it, and set the update interval to 10mS, what tends to happen
> is that because each time you flip a symbol on the lcd you have to wait 2
> jiffies (once before cmd reg, once before writing data reg), and sometimes
> more, because the kernel may put you to sleep a bit longer than the time
> you asked for,  is that the heartbeat starts skipping beats when the io/lan
> leds are changing on/off all the time.
>
> If you really don't like the minimum_on_time, another solution is to leave
> the work queued every five secs, and make sure you service the heartbeat on
> one cycle, and the led/diskio on the other.  If you think that's cleaner, I
> can change it to do that.

Having spent the evening trying that, I've found that the 100mS minimum 
'flutter stop' interval patch gives the most consistent heartbeat of the 
ideas that I've tried. The solution I suggested above causes the heartbeat to 
become uneven when LAN/DISK IO is present still.

If the 100mS minimum LED illumination bothers people who have 'real' LEDs as 
opposed to LCD indicators, we can make it LCD only, of course.


Cheers,

David

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-25 21:50         ` David Pye
@ 2005-03-26  7:13           ` Grant Grundler
  2005-03-26 15:14             ` David Pye
  0 siblings, 1 reply; 16+ messages in thread
From: Grant Grundler @ 2005-03-26  7:13 UTC (permalink / raw)
  To: David Pye; +Cc: parisc-linux

On Fri, Mar 25, 2005 at 09:50:26PM +0000, David Pye wrote:
> On Friday 25 March 2005 17:05, David Pye wrote:
> > > Why not just update every 10ms and ditch all the "minimum on time" stuff?
> > > Can someone tell if an LED/LCD came on 5ms later than it should have
> > > been?
> 
> Ah, I remember now.
> 
> That's 100mS, not 10mS for a start ;)  You can't make the whole update 
> interval 100mS, of course, or you'll miss the heartbeats completely.

Sorry, I wasn't clear. I meant check if LED/LCD status needs to change
every 10ms. The msleep would still let us bang out 8 register writes,
one per HZ. Ie make the "lets see if activity is different now" check
less frequent so LED/LCD is either on or off long enough to be visible.

100ms seems a bit longer than I would like. But if that works, go for it.

> > There is a reason for this, though there may be a better way of doing it.
> >
> > If you remove it, and set the update interval to 10mS, what tends to happen
> > is that because each time you flip a symbol on the lcd you have to wait 2
> > jiffies (once before cmd reg, once before writing data reg), and sometimes
> > more, because the kernel may put you to sleep a bit longer than the time
> > you asked for,  is that the heartbeat starts skipping beats when the io/lan
> > leds are changing on/off all the time.

My point was
1) don't update LED/LCD state again until the previous update has completed.
   ie all the register writes have been sent.

2) we shouldn't touch Heartbeat register for LCD unless heartbeat needs to be updated.

I'm still really not clear why there is "flutter".  THe only reasonable case I can think
of is flutter is an artifact of the CPU busy doing "real" work and not servicing the
LED/LCD wq. i.e. the stress test to excercise networking/disk activity is causing the
CPU to work on lots of other tasks and it's taking more than a few jiffies to get back
to the LED/LCD wq.

> 
> Having spent the evening trying that, I've found that the 100mS minimum 
> 'flutter stop' interval patch gives the most consistent heartbeat of the 
> ideas that I've tried. The solution I suggested above causes the heartbeat to 
> become uneven when LAN/DISK IO is present still.

Yeah, 100ms is well within someone noticing that LED/LCD activity isn't
sync'd with LEDs on NIC or Disk. But I'm ok with that. If they got the real
LED, the display panel can be ignored.

> If the 100mS minimum LED illumination bothers people who have 'real' LEDs as 
> opposed to LCD indicators, we can make it LCD only, of course.

Sure. throw another patch out there! You can enumerate them...this is #3 or #4...

thanks,
grant
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-26  7:13           ` Grant Grundler
@ 2005-03-26 15:14             ` David Pye
  2005-03-26 15:33               ` James Bottomley
  2005-03-27  8:53               ` Grant Grundler
  0 siblings, 2 replies; 16+ messages in thread
From: David Pye @ 2005-03-26 15:14 UTC (permalink / raw)
  To: Grant Grundler; +Cc: parisc-linux


[-- Attachment #1.1.1: Type: text/plain, Size: 3590 bytes --]

On Saturday 26 March 2005 07:13, Grant Grundler wrote:

> Sorry, I wasn't clear. I meant check if LED/LCD status needs to change
> every 10ms. The msleep would still let us bang out 8 register writes,
> one per HZ. Ie make the "lets see if activity is different now" check
> less frequent so LED/LCD is either on or off long enough to be visible.
>
> 100ms seems a bit longer than I would like. But if that works, go for it.

I did try reducing it to 50, and found that it caused heartbeat to become 
irregular under heavy load.

> > > There is a reason for this, though there may be a better way of doing
> > > it.
> > >
> > > If you remove it, and set the update interval to 10mS, what tends to
> > > happen is that because each time you flip a symbol on the lcd you have
> > > to wait 2 jiffies (once before cmd reg, once before writing data reg),
> > > and sometimes more, because the kernel may put you to sleep a bit
> > > longer than the time you asked for,  is that the heartbeat starts
> > > skipping beats when the io/lan leds are changing on/off all the time.
>
> My point was
> 1) don't update LED/LCD state again until the previous update has
> completed. ie all the register writes have been sent.

Yep, we don't do that.

> 2) we shouldn't touch Heartbeat register for LCD unless heartbeat needs to
> be updated.

It already does that too.

> I'm still really not clear why there is "flutter".  THe only reasonable
> case I can think of is flutter is an artifact of the CPU busy doing "real"
> work and not servicing the LED/LCD wq. i.e. the stress test to excercise
> networking/disk activity is causing the CPU to work on lots of other tasks
> and it's taking more than a few jiffies to get back to the LED/LCD wq.

Yeah, it's a little mysterious. What I think is happening is that when we ask 
to msleep between the writes, because we're not busy-waiting, we might be put 
to sleep for far longer than we asked for.  Ditto again, for when we ask to 
be requeued - we ask to run every 10mS but we might end up waiting for far 
longer than that.  That's about the best explanation I've come up with so 
far.  By ensuring that we do not update the io/net symbols more often than 
necessary, we can save ourselves some of these sleeps, so giving the kernel 
less opportunity to put us to sleep!

> > Having spent the evening trying that, I've found that the 100mS minimum
> > 'flutter stop' interval patch gives the most consistent heartbeat of the
> > ideas that I've tried. The solution I suggested above causes the
> > heartbeat to become uneven when LAN/DISK IO is present still.
>
> Yeah, 100ms is well within someone noticing that LED/LCD activity isn't
> sync'd with LEDs on NIC or Disk. But I'm ok with that. If they got the real
> LED, the display panel can be ignored.

Very true.

> > If the 100mS minimum LED illumination bothers people who have 'real' LEDs
> > as opposed to LCD indicators, we can make it LCD only, of course.
>
> Sure. throw another patch out there! You can enumerate them...this is #3 or
> #4...

Attached! The only real change now is that the minimum on-time is now LCD 
only, so hopefully this gets us the best of both worlds. LED people won't 
notice anything different, and the LCD should now be nicely responsive.

Cheers,

David

> thanks,
> grant

-- 
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCS d- s-: a-- C++ UL++++ P L+++ E--- W++ N+ o+ K- w---
O M V- PS+ PE+ Y+ PGP t 5- X+ R- tv+ b+ DI++ D+
G+ e++ h--- r++ y++
------END GEEK CODE BLOCK------

[-- Attachment #1.1.2: lcd_patch4 --]
[-- Type: text/x-diff, Size: 13189 bytes --]

diff -urpN linux-2.6.12-rc1-pa2/arch/parisc/kernel/time.c patched/arch/parisc/kernel/time.c
--- linux-2.6.12-rc1-pa2/arch/parisc/kernel/time.c	2004-10-21 19:58:24.000000000 +0100
+++ patched/arch/parisc/kernel/time.c	2005-03-26 14:30:39.000000000 +0000
@@ -89,14 +89,6 @@ irqreturn_t timer_interrupt(int irq, voi
 		}
 	}
     
-#ifdef CONFIG_CHASSIS_LCD_LED
-	/* Only schedule the led tasklet on cpu 0, and only if it
-	 * is enabled.
-	 */
-	if (cpu == 0 && !atomic_read(&led_tasklet.count))
-		tasklet_schedule(&led_tasklet);
-#endif
-
 	/* check soft power switch status */
 	if (cpu == 0 && !atomic_read(&power_tasklet.count))
 		tasklet_schedule(&power_tasklet);
diff -urpN linux-2.6.12-rc1-pa2/drivers/parisc/led.c patched/drivers/parisc/led.c
--- linux-2.6.12-rc1-pa2/drivers/parisc/led.c	2005-03-18 13:17:10.000000000 +0000
+++ patched/drivers/parisc/led.c	2005-03-26 14:42:35.000000000 +0000
@@ -18,6 +18,9 @@
  * Changes:
  *      - Audit copy_from_user in led_proc_write.
  *                                Daniele Bellucci <bellucda@tiscali.it>
+ *	- Switch from using a tasklet to a work queue, so the led_LCD_driver
+ *	  	can sleep.
+ *	  			  David Pye <dmp@davidmpye.dyndns.org>
  */
 
 #include <linux/config.h>
@@ -37,6 +40,7 @@
 #include <linux/proc_fs.h>
 #include <linux/ctype.h>
 #include <linux/blkdev.h>
+#include <linux/workqueue.h>
 #include <asm/io.h>
 #include <asm/processor.h>
 #include <asm/hardware.h>
@@ -46,9 +50,9 @@
 #include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
-   completely in software. The necessary calculations are done in a tasklet
-   which is scheduled at every timer interrupt and since the calculations 
-   may consume relatively much CPU-time some of the calculations can be 
+   completely in software. The necessary calculations are done in a work queue
+   task which is scheduled regularly, and since the calculations may consume a 
+   relatively large amount of CPU time, some of the calculations can be 
    turned off with the following variables (controlled via procfs) */
 
 static int led_type = -1;
@@ -58,13 +62,16 @@ static int led_lanrxtx = 1;
 static char lcd_text[32];
 static char lcd_text_default[32];
 
+static struct workqueue_struct *led_wq;
+static void led_work_func(void *);
+static DECLARE_WORK(led_task, led_work_func, NULL);
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
 #define DPRINTK(x)
 #endif
 
-
 struct lcd_block {
 	unsigned char command;	/* stores the command byte      */
 	unsigned char on;	/* value for turning LED on     */
@@ -115,12 +122,24 @@ lcd_info __attribute__((aligned(8))) =
 #define LCD_DATA_REG	lcd_info.lcd_data_reg_addr	 
 #define LED_DATA_REG	lcd_info.lcd_cmd_reg_addr	/* LASI & ASP only */
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+
+/* The workqueue must be created at init-time */
+static int start_task(void) 
+{	
+	/* Create the work queue and queue the LED task */
+	led_wq = create_singlethread_workqueue("led_wq");	
+	queue_work(led_wq, &led_task);
+
+	return 0;
+}
+
+device_initcall(start_task);
 
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
-#define LED_HASLCD 1
-#define LED_NOLCD  0
 #ifdef CONFIG_PROC_FS
 static int led_proc_read(char *page, char **start, off_t off, int count, 
 	int *eof, void *data)
@@ -285,53 +304,37 @@ static void led_LASI_driver(unsigned cha
 /*
    ** 
    ** led_LCD_driver()
-   ** 
-   ** The logic of the LCD driver is, that we write at every scheduled call
-   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
-   ** That way we don't need to let this tasklet busywait for min_cmd_delay
-   ** milliseconds.
-   **
-   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
    **   
  */
 static void led_LCD_driver(unsigned char leds)
 {
-	static int last_index;	/* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
-	static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
-	struct lcd_block *block_ptr;
-	int value;
-
-	switch (last_index) {
-	    case 0:	block_ptr = &lcd_info.heartbeat;
-			value = leds & LED_HEARTBEAT;
-			break;
-	    case 1:	block_ptr = &lcd_info.disk_io;
-			value = leds & LED_DISK_IO;
-			break;					
-	    case 2:	block_ptr = &lcd_info.lan_rcv;
-			value = leds & LED_LAN_RCV;
-			break;					
-	    case 3:	block_ptr = &lcd_info.lan_tx;
-			value = leds & LED_LAN_TX;
-			break;
-	    default:	/* should never happen: */
-			return;
-	}
+	static int lastleds, i;
+	static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
+		LED_LAN_RCV, LED_LAN_TX };
+	
+	static struct lcd_block * blockp[4] = {
+		&lcd_info.heartbeat,
+		&lcd_info.disk_io,
+		&lcd_info.lan_rcv,
+		&lcd_info.lan_tx
+	};
 
-	if (last_was_cmd) {
-	    /* write the value to the LCD data port */
-    	    gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
-	} else {
-	    /* write the command-byte to the LCD command register */
-    	    gsc_writeb( block_ptr->command, LCD_CMD_REG );
-	}    
-	
-	/* now update the vars for the next interrupt iteration */ 
-	if (++last_was_cmd == 2) { /* switch between cmd & data */
-	    last_was_cmd = 0;
-	    if (++last_index == 4) 
-		last_index = 0;	 /* switch back to heartbeat index */
+	/* Convert min_cmd_delay to milliseconds */
+	unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000);
+	
+	for (i=0; i<4; ++i) 
+	{
+		if ((leds & mask[i]) != (lastleds & mask[i])) 
+		{
+			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
+			msleep(msec_cmd_delay);
+			
+			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
+					blockp[i]->off, LCD_DATA_REG );
+			msleep(msec_cmd_delay);
+		}
 	}
+	lastleds = leds;
 }
 
 
@@ -355,7 +358,7 @@ static __inline__ int led_get_net_activi
 
 	rx_total = tx_total = 0;
 	
-	/* we are running as tasklet, so locking dev_base 
+	/* we are running as a workqueue task, so locking dev_base 
 	 * for reading should be OK */
 	read_lock(&dev_base_lock);
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -417,10 +420,9 @@ static __inline__ int led_get_diskio_act
 
 
 /*
-   ** led_tasklet_func()
+   ** led_work_func()
    ** 
-   ** is scheduled at every timer interrupt from time.c and
-   ** updates the chassis LCD/LED 
+   ** is scheduled every 5 milliseconds and updates the chassis LCD/LED 
 
     TODO:
     - display load average (older machines like 715/64 have 4 "free" LED's for that)
@@ -431,66 +433,106 @@ static __inline__ int led_get_diskio_act
 #define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
 #define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 
-#define NORMALIZED_COUNT(count) (count/(HZ/100))
+#define LCD_LED_MINIMUM_ON_TIME (HZ*10/100)
 
-static void led_tasklet_func(unsigned long unused)
+#define LED_UPDATE_INTERVAL (1 + (HZ*4/1000))
+
+static void led_work_func (void *unused)
 {
 	static unsigned char lastleds;
-	unsigned char currentleds; /* stores current value of the LEDs */
-	static unsigned long count; /* static incremented value, not wrapped */
 	static unsigned long count_HZ; /* counter in range 0..HZ */
 
+	static unsigned long diskio_on_jiffies;
+	static unsigned long lantx_on_jiffies;
+	static unsigned long lanrcv_on_jiffies;
+
+	unsigned char currentleds = 0; /* stores current value of the LEDs */
+	
 	/* exit if not initialized */
 	if (!led_func_ptr)
 	    return;
 
-	/* increment the local counters */
-	++count;
-	if (++count_HZ == HZ)
+	/* increment the local counter */
+	count_HZ += LED_UPDATE_INTERVAL;
+	if (count_HZ >= HZ)
 	    count_HZ = 0;
 
-	currentleds = lastleds;
-
 	if (led_heartbeat)
 	{
 		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
 		if (count_HZ<HEARTBEAT_LEN || 
 		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
 		    currentleds |= LED_HEARTBEAT;
-		else
-		    currentleds &= ~LED_HEARTBEAT;
 	}
 
-	/* look for network activity and flash LEDs respectively */
-	if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0)
+	/* look for network activity and flash LEDs accordingly */
+	if (led_lanrxtx)
 	{
-		currentleds &= ~(LED_LAN_RCV | LED_LAN_TX);
-		currentleds |= led_get_net_activity();
+		unsigned char new_lan_leds = led_get_net_activity();
+		/* For LCD based systems, enforce a 'minimum on time' to prevent fluttering. */
+		if (led_type == LED_HASLCD) {
+			/* TX LED */
+			if (new_lan_leds & LED_LAN_TX) 
+			{
+				currentleds |= LED_LAN_TX;
+				lantx_on_jiffies = jiffies;
+			}
+			else if (lastleds & LED_LAN_TX && time_before(jiffies, lantx_on_jiffies + LCD_LED_MINIMUM_ON_TIME))
+			{
+				currentleds |= LED_LAN_TX;
+			}
+			
+			/* RCV LED */
+
+			if (new_lan_leds & LED_LAN_RCV) 
+			{
+				currentleds |= LED_LAN_RCV;
+				lanrcv_on_jiffies = jiffies;
+			}
+			else if (lastleds & LED_LAN_RCV && time_before(jiffies, lanrcv_on_jiffies + LCD_LED_MINIMUM_ON_TIME)) 
+			{
+				currentleds |= LED_LAN_RCV;
+			}
+		}
+		else currentleds |= new_lan_leds;
 	}
 
-	/* avoid to calculate diskio-stats at same irq  as netio-stats */
-	if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0)
+	/* Calculate disk IO */
+	if (led_diskio)
 	{
-		currentleds &= ~LED_DISK_IO;
-		currentleds |= led_get_diskio_activity();
+		unsigned char new_io_led = led_get_diskio_activity();
+		/* For LCD based systems, enforce a 'minimum on time' to prevent fluttering. */
+		if (led_type == LED_HASLCD) {
+			if (new_io_led & LED_DISK_IO) 
+			{
+				currentleds |= LED_DISK_IO;
+					diskio_on_jiffies = jiffies;
+			}
+			else if (lastleds & LED_DISK_IO && time_before(jiffies, diskio_on_jiffies + LCD_LED_MINIMUM_ON_TIME)) 
+			{
+				currentleds |= LED_DISK_IO;
+			}
+		}
+		else currentleds |= new_io_led;
 	}
 
 	/* blink all LEDs twice a second if we got an Oops (HPMC) */
-	if (oops_in_progress) {
+	if (oops_in_progress) 
+	{
 		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
 	}
 	
-	/* update the LCD/LEDs */
-	if (currentleds != lastleds) {
-	    led_func_ptr(currentleds);
-	    lastleds = currentleds;
+	/* Update the LCD/LEDs if they've changed */
+	if (currentleds != lastleds)
+	{
+		led_func_ptr(currentleds);
+		lastleds = currentleds;
 	}
+	
+	/* Requeue ourself to run in 5 jiffies */	
+	queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL);
 }
 
-/* main led tasklet struct (scheduled from time.c) */
-DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
-
-
 /*
    ** led_halt()
    ** 
@@ -519,9 +561,13 @@ static int led_halt(struct notifier_bloc
 	default:		return NOTIFY_DONE;
 	}
 	
-	/* completely stop the LED/LCD tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* Cancel the work item and delete the queue */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+		destroy_workqueue(led_wq);
+	}
+ 
 	if (lcd_info.model == DISPLAY_MODEL_LCD)
 		lcd_print(txt);
 	else
@@ -586,9 +632,6 @@ int __init register_led_driver(int model
 	initialized++;
 	register_reboot_notifier(&led_notifier);
 
-	/* start the led tasklet for the first time */
-	tasklet_enable(&led_tasklet);
-	
 	return 0;
 }
 
@@ -623,7 +666,7 @@ void __init register_led_regions(void)
    ** lcd_print()
    ** 
    ** Displays the given string on the LCD-Display of newer machines.
-   ** lcd_print() disables the timer-based led tasklet during its 
+   ** lcd_print() disables the timer-based led work task during its 
    ** execution and enables it afterwards again.
    **
  */
@@ -634,12 +677,14 @@ int lcd_print( char *str )
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
-	/* temporarily disable the led tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* temporarily disable the led work task */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+	}
 	/* copy display string to buffer for procfs */
 	strlcpy(lcd_text, str, sizeof(lcd_text));
-	
+
 	/* Set LCD Cursor to 1st character */
 	gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
 	udelay(lcd_info.min_cmd_delay);
@@ -653,8 +698,10 @@ int lcd_print( char *str )
 	    udelay(lcd_info.min_cmd_delay);
 	}
 	
-	/* re-enable the led tasklet */
-	tasklet_enable(&led_tasklet);
+	/* re-queue the work */
+	if (led_wq) {
+		queue_work(led_wq, &led_task);
+	}
 
 	return lcd_info.lcd_width;
 }
diff -urpN linux-2.6.12-rc1-pa2/include/asm-parisc/led.h patched/include/asm-parisc/led.h
--- linux-2.6.12-rc1-pa2/include/asm-parisc/led.h	2004-12-30 08:07:48.000000000 +0000
+++ patched/include/asm-parisc/led.h	2005-03-26 14:30:39.000000000 +0000
@@ -23,9 +23,6 @@
 
 #define LED_CMD_REG_NONE 0		/* NULL == no addr for the cmd register */
 
-/* led tasklet struct */
-extern struct tasklet_struct led_tasklet;
-
 /* register_led_driver() */
 int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
 

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-26 15:14             ` David Pye
@ 2005-03-26 15:33               ` James Bottomley
       [not found]                 ` <200503261545.05372.dmp@davidmpye.dyndns.org>
  2005-03-27  8:53               ` Grant Grundler
  1 sibling, 1 reply; 16+ messages in thread
From: James Bottomley @ 2005-03-26 15:33 UTC (permalink / raw)
  To: dmp; +Cc: PARISC list

On Sat, 2005-03-26 at 15:14 +0000, David Pye wrote:
> > I'm still really not clear why there is "flutter".  THe only reasonable
> > case I can think of is flutter is an artifact of the CPU busy doing "real"
> > work and not servicing the LED/LCD wq. i.e. the stress test to excercise
> > networking/disk activity is causing the CPU to work on lots of other tasks
> > and it's taking more than a few jiffies to get back to the LED/LCD wq.
> 
> Yeah, it's a little mysterious. What I think is happening is that when we ask 
> to msleep between the writes, because we're not busy-waiting, we might be put 
> to sleep for far longer than we asked for.  Ditto again, for when we ask to 
> be requeued - we ask to run every 10mS but we might end up waiting for far 
> longer than that.  That's about the best explanation I've come up with so 
> far.  By ensuring that we do not update the io/net symbols more often than 
> necessary, we can save ourselves some of these sleeps, so giving the kernel 
> less opportunity to put us to sleep!

This is a natural consequence of converting from a tasklet to a
workqueue.  workqueues and msleep are controlled by the scheduler.  The
guarantee is not that the task will be awoken at the specified interval,
but that the task will be marked ready to run at the specified interval.
When it actually runs is within the gift of the scheduler and depends
entirely on how many other runnable tasks there are and what priorities
they have.

If you want absolute control of the timings then you have to use a
tasklet or timer (as per the original implementation)

James


_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
       [not found]                 ` <200503261545.05372.dmp@davidmpye.dyndns.org>
@ 2005-03-26 15:51                   ` David Pye
  0 siblings, 0 replies; 16+ messages in thread
From: David Pye @ 2005-03-26 15:51 UTC (permalink / raw)
  To: parisc-linux


[-- Attachment #1.1: Type: text/plain, Size: 1748 bytes --]

On Saturday 26 March 2005 15:33, you wrote:

> This is a natural consequence of converting from a tasklet to a
> workqueue.  workqueues and msleep are controlled by the scheduler.  The
> guarantee is not that the task will be awoken at the specified interval,
> but that the task will be marked ready to run at the specified interval.
> When it actually runs is within the gift of the scheduler and depends
> entirely on how many other runnable tasks there are and what priorities
> they have.
>
> If you want absolute control of the timings then you have to use a
> tasklet or timer (as per the original implementation)

Yep, I agree entirely.  The problem with the previous solution arrived because 
of the fact that by nature of the way the LCD updates work, they're not 
atomic - ie there's a minimum write time between each write.  Because 
tasklets cannot sleep, it ends up turning into a can of worms to ensure that 
you waited long enough, etc. 

The old solution of writing the cmd reg one tasklet run, and the data reg the 
next generated some nasty race conditions that Grant spotted - specifically, 
with lcd_print.  It stops the tasklet, does its thing, and restarts it.  
Unfortunately, if the tasklet had written the cmd reg before, it now expects 
to write the data reg next. This seems to be the cause of the missing lcd 
characters you sometimes ended up with if you tried to make heavy use of 
lcd_print.

Personally, I think the workqueue idea is better, partly because of the fact 
that it greatly simplifies led_LCD_driver, and partly because of the fact 
that by running quite a bit less often, should impose lower CPU time usage.
(and not just because I wrote the patch ;)

Cheers,

David

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-26 15:14             ` David Pye
  2005-03-26 15:33               ` James Bottomley
@ 2005-03-27  8:53               ` Grant Grundler
  2005-03-27  9:38                 ` David Pye
  1 sibling, 1 reply; 16+ messages in thread
From: Grant Grundler @ 2005-03-27  8:53 UTC (permalink / raw)
  To: David Pye; +Cc: parisc-linux

On Sat, Mar 26, 2005 at 03:14:41PM +0000, David Pye wrote:
> Attached! The only real change now is that the minimum on-time is now LCD 
> only, so hopefully this gets us the best of both worlds. LED people won't 
> notice anything different, and the LCD should now be nicely responsive.

I've attached the patch I'm willing to commit...works for me.

And I really like the work-queue implementation. It nicely
solves the cmd/data pair problem and consolidates more of the
support in led.c.  The only drawback I can see so far is the
scheduler may chose to not run the led_func when it needs to
be run. If a machine is really busy, only the hearbeat will
suffer and I expect users will get other clues that the machine
is just really busy.

Sorry - I can't get warm to the implementation of minimum on time.
It was really cluttering up the main routine.
Can I commit this patch for now and then we follow up with another
patch for minimum on-time?

I think I would be ok with it implemented inside the led_get*
activity routines. They have access to lastleds and jiffies and
that should be enough to figure out how long an LED should stay
on if it's already on.  The main routine can stay simple/readable.

I also just realized because led_get* routines are static inline,
led_lanrxtx and led_diskio tests can be moved to the respective
routines as well with no harm to performance. May be a bad idea
though since current construct is pretty obvious what they do.

cheers,
grant


Signed-off-by: Grant Grundler <grundler@parisc-linux.org>

Index: arch/parisc/kernel/time.c
===================================================================
RCS file: /var/cvs/linux-2.6/arch/parisc/kernel/time.c,v
retrieving revision 1.9
diff -u -p -r1.9 time.c
--- arch/parisc/kernel/time.c	21 Oct 2004 18:58:24 -0000	1.9
+++ arch/parisc/kernel/time.c	27 Mar 2005 08:33:00 -0000
@@ -89,14 +89,6 @@ irqreturn_t timer_interrupt(int irq, voi
 		}
 	}
     
-#ifdef CONFIG_CHASSIS_LCD_LED
-	/* Only schedule the led tasklet on cpu 0, and only if it
-	 * is enabled.
-	 */
-	if (cpu == 0 && !atomic_read(&led_tasklet.count))
-		tasklet_schedule(&led_tasklet);
-#endif
-
 	/* check soft power switch status */
 	if (cpu == 0 && !atomic_read(&power_tasklet.count))
 		tasklet_schedule(&power_tasklet);
Index: drivers/parisc/led.c
===================================================================
RCS file: /var/cvs/linux-2.6/drivers/parisc/led.c,v
retrieving revision 1.12
diff -u -p -r1.12 led.c
--- drivers/parisc/led.c	18 Mar 2005 13:17:10 -0000	1.12
+++ drivers/parisc/led.c	27 Mar 2005 08:33:00 -0000
@@ -18,6 +18,9 @@
  * Changes:
  *      - Audit copy_from_user in led_proc_write.
  *                                Daniele Bellucci <bellucda@tiscali.it>
+ *	- Switch from using a tasklet to a work queue, so the led_LCD_driver
+ *	  	can sleep.
+ *	  			  David Pye <dmp@davidmpye.dyndns.org>
  */
 
 #include <linux/config.h>
@@ -37,6 +40,7 @@
 #include <linux/proc_fs.h>
 #include <linux/ctype.h>
 #include <linux/blkdev.h>
+#include <linux/workqueue.h>
 #include <asm/io.h>
 #include <asm/processor.h>
 #include <asm/hardware.h>
@@ -46,25 +50,30 @@
 #include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
-   completely in software. The necessary calculations are done in a tasklet
-   which is scheduled at every timer interrupt and since the calculations 
-   may consume relatively much CPU-time some of the calculations can be 
+   completely in software. The necessary calculations are done in a work queue
+   task which is scheduled regularly, and since the calculations may consume a 
+   relatively large amount of CPU time, some of the calculations can be 
    turned off with the following variables (controlled via procfs) */
 
 static int led_type = -1;
-static int led_heartbeat = 1;
-static int led_diskio = 1;
-static int led_lanrxtx = 1;
+static unsigned char lastleds;	/* LED state from most recent update */
+static unsigned int led_heartbeat = 1;
+static unsigned int led_diskio = 1;
+static unsigned int led_lanrxtx = 1;
 static char lcd_text[32];
 static char lcd_text_default[32];
 
+
+static struct workqueue_struct *led_wq;
+static void led_work_func(void *);
+static DECLARE_WORK(led_task, led_work_func, NULL);
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
 #define DPRINTK(x)
 #endif
 
-
 struct lcd_block {
 	unsigned char command;	/* stores the command byte      */
 	unsigned char on;	/* value for turning LED on     */
@@ -115,12 +124,24 @@ lcd_info __attribute__((aligned(8))) =
 #define LCD_DATA_REG	lcd_info.lcd_data_reg_addr	 
 #define LED_DATA_REG	lcd_info.lcd_cmd_reg_addr	/* LASI & ASP only */
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+
+/* The workqueue must be created at init-time */
+static int start_task(void) 
+{	
+	/* Create the work queue and queue the LED task */
+	led_wq = create_singlethread_workqueue("led_wq");	
+	queue_work(led_wq, &led_task);
+
+	return 0;
+}
+
+device_initcall(start_task);
 
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
-#define LED_HASLCD 1
-#define LED_NOLCD  0
 #ifdef CONFIG_PROC_FS
 static int led_proc_read(char *page, char **start, off_t off, int count, 
 	int *eof, void *data)
@@ -285,52 +306,35 @@ static void led_LASI_driver(unsigned cha
 /*
    ** 
    ** led_LCD_driver()
-   ** 
-   ** The logic of the LCD driver is, that we write at every scheduled call
-   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
-   ** That way we don't need to let this tasklet busywait for min_cmd_delay
-   ** milliseconds.
-   **
-   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
    **   
  */
 static void led_LCD_driver(unsigned char leds)
 {
-	static int last_index;	/* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
-	static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
-	struct lcd_block *block_ptr;
-	int value;
-
-	switch (last_index) {
-	    case 0:	block_ptr = &lcd_info.heartbeat;
-			value = leds & LED_HEARTBEAT;
-			break;
-	    case 1:	block_ptr = &lcd_info.disk_io;
-			value = leds & LED_DISK_IO;
-			break;					
-	    case 2:	block_ptr = &lcd_info.lan_rcv;
-			value = leds & LED_LAN_RCV;
-			break;					
-	    case 3:	block_ptr = &lcd_info.lan_tx;
-			value = leds & LED_LAN_TX;
-			break;
-	    default:	/* should never happen: */
-			return;
-	}
-
-	if (last_was_cmd) {
-	    /* write the value to the LCD data port */
-    	    gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
-	} else {
-	    /* write the command-byte to the LCD command register */
-    	    gsc_writeb( block_ptr->command, LCD_CMD_REG );
-	}    
-	
-	/* now update the vars for the next interrupt iteration */ 
-	if (++last_was_cmd == 2) { /* switch between cmd & data */
-	    last_was_cmd = 0;
-	    if (++last_index == 4) 
-		last_index = 0;	 /* switch back to heartbeat index */
+	static int i;
+	static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
+		LED_LAN_RCV, LED_LAN_TX };
+	
+	static struct lcd_block * blockp[4] = {
+		&lcd_info.heartbeat,
+		&lcd_info.disk_io,
+		&lcd_info.lan_rcv,
+		&lcd_info.lan_tx
+	};
+
+	/* Convert min_cmd_delay to milliseconds */
+	unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000);
+	
+	for (i=0; i<4; ++i) 
+	{
+		if ((leds & mask[i]) != (lastleds & mask[i])) 
+		{
+			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
+			msleep(msec_cmd_delay);
+			
+			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
+					blockp[i]->off, LCD_DATA_REG );
+			msleep(msec_cmd_delay);
+		}
 	}
 }
 
@@ -355,7 +359,7 @@ static __inline__ int led_get_net_activi
 
 	rx_total = tx_total = 0;
 	
-	/* we are running as tasklet, so locking dev_base 
+	/* we are running as a workqueue task, so locking dev_base 
 	 * for reading should be OK */
 	read_lock(&dev_base_lock);
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -402,7 +406,7 @@ static __inline__ int led_get_diskio_act
 	static unsigned long last_pgpgin, last_pgpgout;
 	struct page_state pgstat;
 	int changed;
-	
+
 	get_full_page_state(&pgstat); /* get no of sectors in & out */
 
 	/* Just use a very simple calculation here. Do not care about overflow,
@@ -410,86 +414,68 @@ static __inline__ int led_get_diskio_act
 	changed = (pgstat.pgpgin != last_pgpgin) || (pgstat.pgpgout != last_pgpgout);
 	last_pgpgin  = pgstat.pgpgin;
 	last_pgpgout = pgstat.pgpgout;
-	
+
 	return (changed ? LED_DISK_IO : 0);
 }
 
 
 
 /*
-   ** led_tasklet_func()
+   ** led_work_func()
    ** 
-   ** is scheduled at every timer interrupt from time.c and
-   ** updates the chassis LCD/LED 
+   ** manages when and which chassis LCD/LED gets updated
 
     TODO:
     - display load average (older machines like 715/64 have 4 "free" LED's for that)
     - optimizations
  */
 
-#define HEARTBEAT_LEN (HZ*6/100)
-#define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
+#define HEARTBEAT_LEN (HZ*10/100)
+#define HEARTBEAT_2ND_RANGE_START (HZ*28/100)
 #define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 
-#define NORMALIZED_COUNT(count) (count/(HZ/100))
+#define LED_UPDATE_INTERVAL (1 + (HZ*19/1000))
 
-static void led_tasklet_func(unsigned long unused)
+static void led_work_func (void *unused)
 {
-	static unsigned char lastleds;
-	unsigned char currentleds; /* stores current value of the LEDs */
-	static unsigned long count; /* static incremented value, not wrapped */
 	static unsigned long count_HZ; /* counter in range 0..HZ */
+	unsigned char currentleds = 0; /* stores current value of the LEDs */
 
 	/* exit if not initialized */
 	if (!led_func_ptr)
 	    return;
 
-	/* increment the local counters */
-	++count;
-	if (++count_HZ == HZ)
+	/* increment the local counter */
+	count_HZ += LED_UPDATE_INTERVAL;
+	if (count_HZ >= HZ)
 	    count_HZ = 0;
 
-	currentleds = lastleds;
-
 	if (led_heartbeat)
 	{
-		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
-		if (count_HZ<HEARTBEAT_LEN || 
-		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
-		    currentleds |= LED_HEARTBEAT;
-		else
-		    currentleds &= ~LED_HEARTBEAT;
+		/* flash heartbeat-LED like a real heart
+		 * (2 x short then a long delay)
+		 */
+		if (count_HZ < HEARTBEAT_LEN || 
+				(count_HZ >= HEARTBEAT_2ND_RANGE_START &&
+				count_HZ < HEARTBEAT_2ND_RANGE_END)) 
+			currentleds |= LED_HEARTBEAT;
 	}
 
-	/* look for network activity and flash LEDs respectively */
-	if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0)
-	{
-		currentleds &= ~(LED_LAN_RCV | LED_LAN_TX);
-		currentleds |= led_get_net_activity();
-	}
-
-	/* avoid to calculate diskio-stats at same irq  as netio-stats */
-	if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0)
-	{
-		currentleds &= ~LED_DISK_IO;
-		currentleds |= led_get_diskio_activity();
-	}
+	if (led_lanrxtx)  currentleds |= led_get_net_activity();
+	if (led_diskio)   currentleds |= led_get_diskio_activity();
 
 	/* blink all LEDs twice a second if we got an Oops (HPMC) */
-	if (oops_in_progress) {
+	if (oops_in_progress) 
 		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
-	}
-	
-	/* update the LCD/LEDs */
-	if (currentleds != lastleds) {
-	    led_func_ptr(currentleds);
-	    lastleds = currentleds;
-	}
-}
 
-/* main led tasklet struct (scheduled from time.c) */
-DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
+	if (currentleds != lastleds)
+	{
+		led_func_ptr(currentleds);	/* Update the LCD/LEDs */
+		lastleds = currentleds;
+	}
 
+	queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL);
+}
 
 /*
    ** led_halt()
@@ -519,9 +505,13 @@ static int led_halt(struct notifier_bloc
 	default:		return NOTIFY_DONE;
 	}
 	
-	/* completely stop the LED/LCD tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* Cancel the work item and delete the queue */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+		destroy_workqueue(led_wq);
+	}
+ 
 	if (lcd_info.model == DISPLAY_MODEL_LCD)
 		lcd_print(txt);
 	else
@@ -586,9 +576,6 @@ int __init register_led_driver(int model
 	initialized++;
 	register_reboot_notifier(&led_notifier);
 
-	/* start the led tasklet for the first time */
-	tasklet_enable(&led_tasklet);
-	
 	return 0;
 }
 
@@ -623,7 +610,7 @@ void __init register_led_regions(void)
    ** lcd_print()
    ** 
    ** Displays the given string on the LCD-Display of newer machines.
-   ** lcd_print() disables the timer-based led tasklet during its 
+   ** lcd_print() disables the timer-based led work task during its 
    ** execution and enables it afterwards again.
    **
  */
@@ -634,12 +621,14 @@ int lcd_print( char *str )
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
-	/* temporarily disable the led tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* temporarily disable the led work task */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+	}
 	/* copy display string to buffer for procfs */
 	strlcpy(lcd_text, str, sizeof(lcd_text));
-	
+
 	/* Set LCD Cursor to 1st character */
 	gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
 	udelay(lcd_info.min_cmd_delay);
@@ -653,8 +642,10 @@ int lcd_print( char *str )
 	    udelay(lcd_info.min_cmd_delay);
 	}
 	
-	/* re-enable the led tasklet */
-	tasklet_enable(&led_tasklet);
+	/* re-queue the work */
+	if (led_wq) {
+		queue_work(led_wq, &led_task);
+	}
 
 	return lcd_info.lcd_width;
 }
Index: include/asm-parisc/led.h
===================================================================
RCS file: /var/cvs/linux-2.6/include/asm-parisc/led.h,v
retrieving revision 1.3
diff -u -p -r1.3 led.h
--- include/asm-parisc/led.h	30 Dec 2004 08:07:48 -0000	1.3
+++ include/asm-parisc/led.h	27 Mar 2005 08:33:00 -0000
@@ -23,9 +23,6 @@
 
 #define LED_CMD_REG_NONE 0		/* NULL == no addr for the cmd register */
 
-/* led tasklet struct */
-extern struct tasklet_struct led_tasklet;

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-27  8:53               ` Grant Grundler
@ 2005-03-27  9:38                 ` David Pye
  2005-03-27 17:55                   ` David Pye
  2005-03-28  2:13                   ` Grant Grundler
  0 siblings, 2 replies; 16+ messages in thread
From: David Pye @ 2005-03-27  9:38 UTC (permalink / raw)
  To: Grant Grundler; +Cc: parisc-linux


[-- Attachment #1.1: Type: text/plain, Size: 2660 bytes --]

On Sunday 27 March 2005 09:53, Grant Grundler wrote:
> On Sat, Mar 26, 2005 at 03:14:41PM +0000, David Pye wrote:
> > Attached! The only real change now is that the minimum on-time is now LCD
> > only, so hopefully this gets us the best of both worlds. LED people won't
> > notice anything different, and the LCD should now be nicely responsive.
>
> I've attached the patch I'm willing to commit...works for me.
>
> And I really like the work-queue implementation. It nicely
> solves the cmd/data pair problem and consolidates more of the
> support in led.c.  The only drawback I can see so far is the
> scheduler may chose to not run the led_func when it needs to
> be run. If a machine is really busy, only the hearbeat will
> suffer and I expect users will get other clues that the machine
> is just really busy.

Yep, that's what I'd guess too.

> Sorry - I can't get warm to the implementation of minimum on time.
> It was really cluttering up the main routine.
> Can I commit this patch for now and then we follow up with another
> patch for minimum on-time?

Sure.  It definitely cluttered up the main routine, and I wasn't particularly 
happy with having to include an if conditional testing led_type in the 
generic routine.  Hopefully a better idea on how to handle it will emerge 
shortly.

> I think I would be ok with it implemented inside the led_get*
> activity routines. They have access to lastleds and jiffies and
> that should be enough to figure out how long an LED should stay
> on if it's already on.  The main routine can stay simple/readable.

Yep, I hadn't really thought of doing it in there - I'll have a play with it 
and maybe annoy everyone with another patch in a bit.   Downside is that it's 
not really the right place to put it *if* you want it to be lcd-only.

The only other way to do it which you might not find as ugly is to move this 
into the drivers.  ie the drivers would need to be given the responsibility 
of deciding whether they need to update anything (ie each of them has to 
store currentleds rather than the generic calculator function). That way, 
they'll get called each time the work runs, but can decide for themselves 
whether to actually do anything.  It's only a couple of lines to add to the 
other functions e.g. lasi.   How does that sound?

> I also just realized because led_get* routines are static inline,
> led_lanrxtx and led_diskio tests can be moved to the respective
> routines as well with no harm to performance. May be a bad idea
> though since current construct is pretty obvious what they do.
>
> cheers,
> grant

Cheers,

David

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-27  9:38                 ` David Pye
@ 2005-03-27 17:55                   ` David Pye
  2005-03-28  1:51                     ` Grant Grundler
  2005-03-28  2:13                   ` Grant Grundler
  1 sibling, 1 reply; 16+ messages in thread
From: David Pye @ 2005-03-27 17:55 UTC (permalink / raw)
  To: parisc-linux


[-- Attachment #1.1.1: Type: text/plain, Size: 511 bytes --]

Right, I've actually found and fixed what was the cause of the missing 
heartbeats.  Although led minimum ontime 'fixes' it, it's actually a bandaid 
for the real problem.

Could I ask you to have a glance at this patch? It's based on your previous 
one, and only has a very minor change to the way count_HZ is incremented - ie 
increment by the actual jiffies past, not the number we asked to be 
rescheduled in.

On my system, the heart now beats regularly under IO/LAN traffic!

Cheers,

David

[-- Attachment #1.1.2: led_lcd_patch5 --]
[-- Type: text/x-diff, Size: 12652 bytes --]

diff -urpN oldtree/arch/parisc/kernel/time.c newtree/arch/parisc/kernel/time.c
--- oldtree/arch/parisc/kernel/time.c	2004-10-21 19:58:24.000000000 +0100
+++ newtree/arch/parisc/kernel/time.c	2005-03-27 18:30:29.000000000 +0100
@@ -89,14 +89,6 @@ irqreturn_t timer_interrupt(int irq, voi
 		}
 	}
     
-#ifdef CONFIG_CHASSIS_LCD_LED
-	/* Only schedule the led tasklet on cpu 0, and only if it
-	 * is enabled.
-	 */
-	if (cpu == 0 && !atomic_read(&led_tasklet.count))
-		tasklet_schedule(&led_tasklet);
-#endif
-
 	/* check soft power switch status */
 	if (cpu == 0 && !atomic_read(&power_tasklet.count))
 		tasklet_schedule(&power_tasklet);
diff -urpN oldtree/drivers/parisc/led.c newtree/drivers/parisc/led.c
--- oldtree/drivers/parisc/led.c	2005-03-18 13:17:10.000000000 +0000
+++ newtree/drivers/parisc/led.c	2005-03-27 18:31:33.000000000 +0100
@@ -18,6 +18,9 @@
  * Changes:
  *      - Audit copy_from_user in led_proc_write.
  *                                Daniele Bellucci <bellucda@tiscali.it>
+ *	- Switch from using a tasklet to a work queue, so the led_LCD_driver
+ *	  	can sleep.
+ *	  			  David Pye <dmp@davidmpye.dyndns.org>
  */
 
 #include <linux/config.h>
@@ -37,6 +40,7 @@
 #include <linux/proc_fs.h>
 #include <linux/ctype.h>
 #include <linux/blkdev.h>
+#include <linux/workqueue.h>
 #include <asm/io.h>
 #include <asm/processor.h>
 #include <asm/hardware.h>
@@ -46,25 +50,30 @@
 #include <asm/uaccess.h>
 
 /* The control of the LEDs and LCDs on PARISC-machines have to be done 
-   completely in software. The necessary calculations are done in a tasklet
-   which is scheduled at every timer interrupt and since the calculations 
-   may consume relatively much CPU-time some of the calculations can be 
+   completely in software. The necessary calculations are done in a work queue
+   task which is scheduled regularly, and since the calculations may consume a 
+   relatively large amount of CPU time, some of the calculations can be 
    turned off with the following variables (controlled via procfs) */
 
 static int led_type = -1;
-static int led_heartbeat = 1;
-static int led_diskio = 1;
-static int led_lanrxtx = 1;
+static unsigned char lastleds;	/* LED state from most recent update */
+static unsigned int led_heartbeat = 1;
+static unsigned int led_diskio = 1;
+static unsigned int led_lanrxtx = 1;
 static char lcd_text[32];
 static char lcd_text_default[32];
 
+
+static struct workqueue_struct *led_wq;
+static void led_work_func(void *);
+static DECLARE_WORK(led_task, led_work_func, NULL);
+
 #if 0
 #define DPRINTK(x)	printk x
 #else
 #define DPRINTK(x)
 #endif
 
-
 struct lcd_block {
 	unsigned char command;	/* stores the command byte      */
 	unsigned char on;	/* value for turning LED on     */
@@ -115,12 +124,24 @@ lcd_info __attribute__((aligned(8))) =
 #define LCD_DATA_REG	lcd_info.lcd_data_reg_addr	 
 #define LED_DATA_REG	lcd_info.lcd_cmd_reg_addr	/* LASI & ASP only */
 
+#define LED_HASLCD 1
+#define LED_NOLCD  0
+
+/* The workqueue must be created at init-time */
+static int start_task(void) 
+{	
+	/* Create the work queue and queue the LED task */
+	led_wq = create_singlethread_workqueue("led_wq");	
+	queue_work(led_wq, &led_task);
+
+	return 0;
+}
+
+device_initcall(start_task);
 
 /* ptr to LCD/LED-specific function */
 static void (*led_func_ptr) (unsigned char);
 
-#define LED_HASLCD 1
-#define LED_NOLCD  0
 #ifdef CONFIG_PROC_FS
 static int led_proc_read(char *page, char **start, off_t off, int count, 
 	int *eof, void *data)
@@ -285,52 +306,35 @@ static void led_LASI_driver(unsigned cha
 /*
    ** 
    ** led_LCD_driver()
-   ** 
-   ** The logic of the LCD driver is, that we write at every scheduled call
-   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
-   ** That way we don't need to let this tasklet busywait for min_cmd_delay
-   ** milliseconds.
-   **
-   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
    **   
  */
 static void led_LCD_driver(unsigned char leds)
 {
-	static int last_index;	/* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
-	static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
-	struct lcd_block *block_ptr;
-	int value;
-
-	switch (last_index) {
-	    case 0:	block_ptr = &lcd_info.heartbeat;
-			value = leds & LED_HEARTBEAT;
-			break;
-	    case 1:	block_ptr = &lcd_info.disk_io;
-			value = leds & LED_DISK_IO;
-			break;					
-	    case 2:	block_ptr = &lcd_info.lan_rcv;
-			value = leds & LED_LAN_RCV;
-			break;					
-	    case 3:	block_ptr = &lcd_info.lan_tx;
-			value = leds & LED_LAN_TX;
-			break;
-	    default:	/* should never happen: */
-			return;
-	}
+	static int i;
+	static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
+		LED_LAN_RCV, LED_LAN_TX };
+	
+	static struct lcd_block * blockp[4] = {
+		&lcd_info.heartbeat,
+		&lcd_info.disk_io,
+		&lcd_info.lan_rcv,
+		&lcd_info.lan_tx
+	};
 
-	if (last_was_cmd) {
-	    /* write the value to the LCD data port */
-    	    gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
-	} else {
-	    /* write the command-byte to the LCD command register */
-    	    gsc_writeb( block_ptr->command, LCD_CMD_REG );
-	}    
-	
-	/* now update the vars for the next interrupt iteration */ 
-	if (++last_was_cmd == 2) { /* switch between cmd & data */
-	    last_was_cmd = 0;
-	    if (++last_index == 4) 
-		last_index = 0;	 /* switch back to heartbeat index */
+	/* Convert min_cmd_delay to milliseconds */
+	unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000);
+	
+	for (i=0; i<4; ++i) 
+	{
+		if ((leds & mask[i]) != (lastleds & mask[i])) 
+		{
+			gsc_writeb( blockp[i]->command, LCD_CMD_REG );
+			msleep(msec_cmd_delay);
+			
+			gsc_writeb( leds & mask[i] ? blockp[i]->on : 
+					blockp[i]->off, LCD_DATA_REG );
+			msleep(msec_cmd_delay);
+		}
 	}
 }
 
@@ -355,7 +359,7 @@ static __inline__ int led_get_net_activi
 
 	rx_total = tx_total = 0;
 	
-	/* we are running as tasklet, so locking dev_base 
+	/* we are running as a workqueue task, so locking dev_base 
 	 * for reading should be OK */
 	read_lock(&dev_base_lock);
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -402,7 +406,7 @@ static __inline__ int led_get_diskio_act
 	static unsigned long last_pgpgin, last_pgpgout;
 	struct page_state pgstat;
 	int changed;
-	
+
 	get_full_page_state(&pgstat); /* get no of sectors in & out */
 
 	/* Just use a very simple calculation here. Do not care about overflow,
@@ -410,86 +414,70 @@ static __inline__ int led_get_diskio_act
 	changed = (pgstat.pgpgin != last_pgpgin) || (pgstat.pgpgout != last_pgpgout);
 	last_pgpgin  = pgstat.pgpgin;
 	last_pgpgout = pgstat.pgpgout;
-	
+
 	return (changed ? LED_DISK_IO : 0);
 }
 
 
 
 /*
-   ** led_tasklet_func()
+   ** led_work_func()
    ** 
-   ** is scheduled at every timer interrupt from time.c and
-   ** updates the chassis LCD/LED 
+   ** manages when and which chassis LCD/LED gets updated
 
     TODO:
     - display load average (older machines like 715/64 have 4 "free" LED's for that)
     - optimizations
  */
 
-#define HEARTBEAT_LEN (HZ*6/100)
-#define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
+#define HEARTBEAT_LEN (HZ*10/100)
+#define HEARTBEAT_2ND_RANGE_START (HZ*28/100)
 #define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 
-#define NORMALIZED_COUNT(count) (count/(HZ/100))
+#define LED_UPDATE_INTERVAL (1 + (HZ*19/1000))
 
-static void led_tasklet_func(unsigned long unused)
+static void led_work_func (void *unused)
 {
-	static unsigned char lastleds;
-	unsigned char currentleds; /* stores current value of the LEDs */
-	static unsigned long count; /* static incremented value, not wrapped */
+	static unsigned long last_jiffies = 0;
 	static unsigned long count_HZ; /* counter in range 0..HZ */
+	unsigned char currentleds = 0; /* stores current value of the LEDs */
 
 	/* exit if not initialized */
 	if (!led_func_ptr)
 	    return;
 
-	/* increment the local counters */
-	++count;
-	if (++count_HZ == HZ)
+	/* increment the local counter */
+	count_HZ += jiffies - last_jiffies;
+	if (count_HZ >= HZ)
 	    count_HZ = 0;
-
-	currentleds = lastleds;
+	last_jiffies = jiffies;
 
 	if (led_heartbeat)
 	{
-		/* flash heartbeat-LED like a real heart (2 x short then a long delay) */
-		if (count_HZ<HEARTBEAT_LEN || 
-		    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
-		    currentleds |= LED_HEARTBEAT;
-		else
-		    currentleds &= ~LED_HEARTBEAT;
-	}
-
-	/* look for network activity and flash LEDs respectively */
-	if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0)
-	{
-		currentleds &= ~(LED_LAN_RCV | LED_LAN_TX);
-		currentleds |= led_get_net_activity();
+		/* flash heartbeat-LED like a real heart
+		 * (2 x short then a long delay)
+		 */
+		if (count_HZ < HEARTBEAT_LEN || 
+				(count_HZ >= HEARTBEAT_2ND_RANGE_START &&
+				count_HZ < HEARTBEAT_2ND_RANGE_END)) 
+			currentleds |= LED_HEARTBEAT;
 	}
 
-	/* avoid to calculate diskio-stats at same irq  as netio-stats */
-	if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0)
-	{
-		currentleds &= ~LED_DISK_IO;
-		currentleds |= led_get_diskio_activity();
-	}
+	if (led_lanrxtx)  currentleds |= led_get_net_activity();
+	if (led_diskio)   currentleds |= led_get_diskio_activity();
 
 	/* blink all LEDs twice a second if we got an Oops (HPMC) */
-	if (oops_in_progress) {
+	if (oops_in_progress) 
 		currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff;
-	}
-	
-	/* update the LCD/LEDs */
-	if (currentleds != lastleds) {
-	    led_func_ptr(currentleds);
-	    lastleds = currentleds;
-	}
-}
 
-/* main led tasklet struct (scheduled from time.c) */
-DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
+	if (currentleds != lastleds)
+	{
+		led_func_ptr(currentleds);	/* Update the LCD/LEDs */
+		lastleds = currentleds;
+	}
 
+	queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL);
+}
 
 /*
    ** led_halt()
@@ -519,9 +507,13 @@ static int led_halt(struct notifier_bloc
 	default:		return NOTIFY_DONE;
 	}
 	
-	/* completely stop the LED/LCD tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* Cancel the work item and delete the queue */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+		destroy_workqueue(led_wq);
+	}
+ 
 	if (lcd_info.model == DISPLAY_MODEL_LCD)
 		lcd_print(txt);
 	else
@@ -586,9 +578,6 @@ int __init register_led_driver(int model
 	initialized++;
 	register_reboot_notifier(&led_notifier);
 
-	/* start the led tasklet for the first time */
-	tasklet_enable(&led_tasklet);
-	
 	return 0;
 }
 
@@ -623,7 +612,7 @@ void __init register_led_regions(void)
    ** lcd_print()
    ** 
    ** Displays the given string on the LCD-Display of newer machines.
-   ** lcd_print() disables the timer-based led tasklet during its 
+   ** lcd_print() disables the timer-based led work task during its 
    ** execution and enables it afterwards again.
    **
  */
@@ -634,12 +623,14 @@ int lcd_print( char *str )
 	if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 	    return 0;
 	
-	/* temporarily disable the led tasklet */
-	tasklet_disable(&led_tasklet);
-
+	/* temporarily disable the led work task */
+	if (led_wq) {
+		if (cancel_delayed_work(&led_task) == 0) 
+			flush_workqueue(led_wq);
+	}
 	/* copy display string to buffer for procfs */
 	strlcpy(lcd_text, str, sizeof(lcd_text));
-	
+
 	/* Set LCD Cursor to 1st character */
 	gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
 	udelay(lcd_info.min_cmd_delay);
@@ -653,8 +644,10 @@ int lcd_print( char *str )
 	    udelay(lcd_info.min_cmd_delay);
 	}
 	
-	/* re-enable the led tasklet */
-	tasklet_enable(&led_tasklet);
+	/* re-queue the work */
+	if (led_wq) {
+		queue_work(led_wq, &led_task);
+	}
 
 	return lcd_info.lcd_width;
 }
diff -urpN oldtree/include/asm-parisc/led.h newtree/include/asm-parisc/led.h
--- oldtree/include/asm-parisc/led.h	2004-12-30 08:07:48.000000000 +0000
+++ newtree/include/asm-parisc/led.h	2005-03-27 18:30:29.000000000 +0100
@@ -23,9 +23,6 @@
 
 #define LED_CMD_REG_NONE 0		/* NULL == no addr for the cmd register */
 
-/* led tasklet struct */
-extern struct tasklet_struct led_tasklet;
-
 /* register_led_driver() */
 int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
 

[-- Attachment #1.2: Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-27 17:55                   ` David Pye
@ 2005-03-28  1:51                     ` Grant Grundler
  0 siblings, 0 replies; 16+ messages in thread
From: Grant Grundler @ 2005-03-28  1:51 UTC (permalink / raw)
  To: David Pye; +Cc: parisc-linux

On Sun, Mar 27, 2005 at 05:55:53PM +0000, David Pye wrote:
> Could I ask you to have a glance at this patch? It's based on your previous 
> one, and only has a very minor change to the way count_HZ is incremented - ie 
> increment by the actual jiffies past, not the number we asked to be 
> rescheduled in.

Funny you point that out. I was thinking about it yesterday before I posted
the patch but was "too lazy" to figure out how to do it.

(TBH, I had several other things going on).

> On my system, the heart now beats regularly under IO/LAN traffic!

Cool!

> -	/* increment the local counters */
> -	++count;
> -	if (++count_HZ == HZ)
> +	/* increment the local counter */
> +	count_HZ += jiffies - last_jiffies;
> +	if (count_HZ >= HZ)
>  	    count_HZ = 0;
> -
> -	currentleds = lastleds;
> +	last_jiffies = jiffies;

Yeah, I'll add this bit, try it out and commit.

thanks,
grant
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [parisc-linux] J5000 LED LCD patch (take 2)
  2005-03-27  9:38                 ` David Pye
  2005-03-27 17:55                   ` David Pye
@ 2005-03-28  2:13                   ` Grant Grundler
  1 sibling, 0 replies; 16+ messages in thread
From: Grant Grundler @ 2005-03-28  2:13 UTC (permalink / raw)
  To: David Pye; +Cc: parisc-linux

On Sun, Mar 27, 2005 at 09:38:38AM +0000, David Pye wrote:
> > I think I would be ok with it implemented inside the led_get*
> > activity routines. They have access to lastleds and jiffies and
> > that should be enough to figure out how long an LED should stay
> > on if it's already on.  The main routine can stay simple/readable.
> 
> Yep, I hadn't really thought of doing it in there - I'll have a play with it 
> and maybe annoy everyone with another patch in a bit.   Downside is that it's 
> not really the right place to put it *if* you want it to be lcd-only.

I don't really care if it's LCD only.
LCD clearly has different response times than LED.

> The only other way to do it which you might not find as ugly is to move this 
> into the drivers.  ie the drivers would need to be given the responsibility 
> of deciding whether they need to update anything (ie each of them has to 
> store currentleds rather than the generic calculator function). That way, 
> they'll get called each time the work runs, but can decide for themselves 
> whether to actually do anything.  It's only a couple of lines to add to the 
> other functions e.g. lasi.   How does that sound?

Yeah - having the LCD_driver regulate minimum on probably makes just as
much sense since LED really doesn't need it.

Implementing it in led_get* routines was just my first thought.
led_get* controls state of the LEDs and could avoid extra calculations
if it knows about minimum-on time. 
Maybe minimum-on time should be a parameter that's different for LED/LCD
if a hardcoded value isn't acceptable for both.

thanks,
grant
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2005-03-28  2:13 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-03-24 18:48 [parisc-linux] J5000 LED LCD patch (take 2) David Pye
2005-03-24 20:01 ` [parisc-linux] A second LCD patch David Pye
2005-03-24 20:05 ` [parisc-linux] J5000 LED LCD patch (take 2) James Bottomley
2005-03-24 21:00   ` David Pye
2005-03-25  6:25     ` Grant Grundler
2005-03-25 17:05       ` David Pye
2005-03-25 21:50         ` David Pye
2005-03-26  7:13           ` Grant Grundler
2005-03-26 15:14             ` David Pye
2005-03-26 15:33               ` James Bottomley
     [not found]                 ` <200503261545.05372.dmp@davidmpye.dyndns.org>
2005-03-26 15:51                   ` David Pye
2005-03-27  8:53               ` Grant Grundler
2005-03-27  9:38                 ` David Pye
2005-03-27 17:55                   ` David Pye
2005-03-28  1:51                     ` Grant Grundler
2005-03-28  2:13                   ` Grant Grundler

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox