[PATCH 1/3] staging:panel: Rewrite for fixing synchronization issues

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Partial rewrite of panel driver to make it more robust. The old code contains
problems potentially leading to kernel oops, deadlocks and memory leaks.

The patch contains the following changes:
* Handle asynchronous attach/detach to/from parport.
* Proper synchronization of multiple opens to same device.
* Proper disabling of softirqs when locking pprt from user context.
* Replaced interruptible_sleep_on in keypad_read to avoid race conditions.
* Changed to del_timer_sync to make sure timer function finishes before
  unloading module.
* Added owner to file_operations to avoid module being unloaded with dangling
  opens.
* Check status of user-mode memory write in keypad_read.
* Check for and handle errors in keypad_init.
* Properly free memory for logical_inputs to avoid memory leak on unload.

Signed-off-by: Zoltan Kelemen <zoltan@xxxxxxxxxx>
----
diff -ru linux-next/drivers/staging/panel/panel.c panel-patch1/drivers/staging/panel/panel.c
--- linux-next/drivers/staging/panel/panel.c	2012-06-27 05:03:21.000000000 +0200
+++ panel-patch1/drivers/staging/panel/panel.c	2012-06-28 11:49:21.034467337 +0200
@@ -1,6 +1,7 @@
 /*
  * Front panel driver for Linux
  * Copyright (C) 2000-2008, Willy Tarreau <w@xxxxxx>
+ * Partially rewritten 2012, Zoltan Kelemen <zoltan@xxxxxxxxxx>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -21,14 +22,9 @@
  * Several profiles are provided for commonly found LCD+keypad modules on the
  * market, such as those found in Nexcom's appliances.
  *
- * FIXME:
- *      - the initialization/deinitialization process is very dirty and should
- *        be rewritten. It may even be buggy.
- *
  * TODO:
  *	- document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs)
  *      - make the LCD a part of a virtual screen of Vx*Vy
- *	- make the inputs list smp-safe
  *      - change the keyboard to a double mapping : signals -> key_id -> values
  *        so that applications can change values without knowing signals
  *
@@ -62,7 +58,7 @@
 #define LCD_MINOR		156
 #define KEYPAD_MINOR		185
 
-#define PANEL_VERSION		"0.9.5"
+#define PANEL_VERSION		"0.9.6"
 
 #define LCD_MAXBYTES		256	/* max burst write */
 
@@ -70,6 +66,7 @@
 
 /* poll the keyboard this every second */
 #define INPUT_POLL_TIME		(HZ/50)
+
 /* a key starts to repeat after this times INPUT_POLL_TIME */
 #define KEYPAD_REP_START	(10)
 /* a key repeats this times INPUT_POLL_TIME */
@@ -129,7 +126,7 @@
 #define LCD_FLAG_L		0x0080	/* backlight enabled */
 
 #define LCD_ESCAPE_LEN		24	/* max chars for LCD escape command */
-#define LCD_ESCAPE_CHAR	27	/* use char 27 for escape command */
+#define LCD_ESCAPE_CHAR		27	/* use char 27 for escape command */
 
 /* macros to simplify use of the parallel port */
 #define r_ctr(x)        (parport_read_control((x)->port))
@@ -138,12 +135,6 @@
 #define w_ctr(x, y)     do { parport_write_control((x)->port, (y)); } while (0)
 #define w_dtr(x, y)     do { parport_write_data((x)->port, (y)); } while (0)
 
-/* this defines which bits are to be used and which ones to be ignored */
-/* logical or of the output bits involved in the scan matrix */
-static __u8 scan_mask_o;
-/* logical or of the input bits involved in the scan matrix */
-static __u8 scan_mask_i;
-
 typedef __u64 pmask_t;
 
 enum input_type {
@@ -183,8 +174,6 @@
 	} u;
 };
 
-LIST_HEAD(logical_inputs);	/* list of all defined logical inputs */
-
 /* physical contacts history
  * Physical contacts are a 45 bits string of 9 groups of 5 bits each.
  * The 8 lower groups correspond to output bits 0 to 7, and the 9th group
@@ -196,6 +185,9 @@
  * <-----unused------><gnd><d07><d06><d05><d04><d03><d02><d01><d00>
  */
 
+/*
+ * Group of keypad variables accessed only from timer function.
+ */
 /* what has just been read from the I/O ports */
 static pmask_t phys_read;
 /* previous phys_read */
@@ -207,25 +199,67 @@
 /* 0 means that at least one logical signal needs be computed */
 static char inputs_stable;
 
-/* these variables are specific to the keypad */
+/*
+ * Group of keypad variables shared between timer/process context code,
+ * protected by a spinlock. Keypresses are stored at the write end of the
+ * circular buffer, when the keypad device is open.
+ */
+static DEFINE_SPINLOCK(keypad_lock);
+static int keypad_open_cnt;
 static char keypad_buffer[KEYPAD_BUFFER];
-static int keypad_buflen;
 static int keypad_start;
-static char keypressed;
-static wait_queue_head_t keypad_read_wait;
+static int keypad_buflen;
+
+/* Wait queue shared by process/timer context used for blocked reads */
+static DECLARE_WAIT_QUEUE_HEAD(keypad_read_wait);
+
+/*
+ * Keypad variables initialized once when attaching to parallel port, there-
+ * after used as readonly or updated only from timer function.
+ */
+static int keypad_initialized;       /* Nonzero when vars below initialized */
+static LIST_HEAD(logical_inputs);    /* List of all defined logical inputs */
+static __u8 scan_mask_o;             /* Output bits involved in scan matrix */
+static __u8 scan_mask_i;             /* Input bits involved in scan matrix */
+static int keypressed;               /* Key pressed (light backlight) */
+
+/* LCD vars used from timer */
+static int light_tempo;              /* Time left before backlight off */
+static int backlight_on;             /* Nonzero when backlight is on */
+static struct timer_list scan_timer; /* Timer used for keypad scanning */
+static int timer_running;            /* Nonzero when timer enabled */
 
-/* lcd-specific variables */
+/*
+ * Group of parallel-port-related variables. They are protected in process
+ * context with a mutex.
+ *
+ * The system basically has two states: port available (pprt != NULL) and port
+ * unavailable (pprt == NULL). When the port is available all globals are
+ * initialized and valid and the timer function may be running. When port is
+ * unavailable the timer is not running and most globals are undefined.
+ */
+static DEFINE_MUTEX(port_mutex);     /* Protects variables below */
+static struct pardevice *pprt;       /* Non-NULL when port is available */
+static unsigned long int lcd_flags;  /* Contains the LCD config state */
+static unsigned long int lcd_addr_x; /* Contains the LCD X offset */
+static unsigned long int lcd_addr_y; /* Contains the LCD Y offset */
+static char lcd_must_clear;          /* Clear display on next device open */
+static char lcd_escape[LCD_ESCAPE_LEN+1]; /* Current escape sequence */
+static int lcd_escape_len;           /* >=0 = escape cmd len, -1 no escape */
+
+/*
+ * Protects port accesses between timer/process context code and the variables
+ * below.
+ */
+static DEFINE_SPINLOCK(pprt_lock);
+static int backlight_flash;          /* Turn on backlight for short time */
+static int force_backlight_on;       /* Nonzero to lock backlight on */
 
-/* contains the LCD config state */
-static unsigned long int lcd_flags;
-/* contains the LCD X offset */
-static unsigned long int lcd_addr_x;
-/* contains the LCD Y offset */
-static unsigned long int lcd_addr_y;
-/* current escape sequence, 0 terminated */
-static char lcd_escape[LCD_ESCAPE_LEN + 1];
-/* not in escape state. >=0 = escape cmd len */
-static int lcd_escape_len = -1;
+static unsigned long lcd_is_open;    /* Nonzero if LCD device opened */
+
+static void (*lcd_write_cmd) (int);
+static void (*lcd_write_data) (int);
+static void (*lcd_clear_fast) (void);
 
 /*
  * Bit masks to convert LCD signals to parallel port outputs.
@@ -401,27 +435,6 @@
 
 #endif /* DEFAULT_PROFILE == 0 */
 
-/* global variables */
-static int keypad_open_cnt;	/* #times opened */
-static int lcd_open_cnt;	/* #times opened */
-static struct pardevice *pprt;
-
-static int lcd_initialized;
-static int keypad_initialized;
-
-static int light_tempo;
-
-static char lcd_must_clear;
-static char lcd_left_shift;
-static char init_in_progress;
-
-static void (*lcd_write_cmd) (int);
-static void (*lcd_write_data) (int);
-static void (*lcd_clear_fast) (void);
-
-static DEFINE_SPINLOCK(pprt_lock);
-static struct timer_list scan_timer;
-
 MODULE_DESCRIPTION("Generic parallel port LCD/Keypad driver");
 
 static int parport = -1;
@@ -609,7 +622,7 @@
 	unsigned char da; /* serial LCD data */
 } bits;
 
-static void init_scan_timer(void);
+static void start_scan_timer(void);
 
 /* sets data port bits according to current signals values */
 static int set_data_bits(void)
@@ -667,7 +680,7 @@
  *   out(dport, in(dport) & d_val[2] | d_val[signal_state])
  *   out(cport, in(cport) & c_val[2] | c_val[signal_state])
  */
-void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val)
+static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val)
 {
 	int d_bit, c_bit, inv;
 
@@ -748,45 +761,74 @@
 	}
 }
 
-/* turn the backlight on or off */
-static void lcd_backlight(int on)
+
+/*
+ * Lock/unlock parallel port for use between timer/process context. Used by
+ * most low-level functions accessing the port. Assumed that caller is
+ * process context since we disable softirqs. The timer function locks the port
+ * itself and only calls unlocked functions.
+ */
+static void lock_pprt(void)
+{
+	spin_lock_bh(&pprt_lock);
+}
+
+static void unlock_pprt(void)
+{
+	spin_unlock_bh(&pprt_lock);
+}
+
+
+/* turn the backlight on or off. assumes caller grabbed the spin lock */
+static void lcd_backlight_unlocked(int on)
 {
 	if (lcd_bl_pin == PIN_NONE)
 		return;
 
-	/* The backlight is activated by setting the AUTOFEED line to +5V  */
-	spin_lock(&pprt_lock);
+	/* The backlight is activated by setting the backlight line */
 	bits.bl = on;
+	backlight_on = on;
 	panel_set_bits();
-	spin_unlock(&pprt_lock);
+}
+
+/*
+ * Turn backlight on from process context. Remember backlight setting, if
+ * enabled here it is not turned off when expired in the timer.
+ */
+static void lcd_backlight(int on)
+{
+	lock_pprt();
+	lcd_backlight_unlocked(on);
+	force_backlight_on = on;
+	unlock_pprt();
 }
 
 /* send a command to the LCD panel in serial mode */
 static void lcd_write_cmd_s(int cmd)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	lcd_send_serial(0x1F);	/* R/W=W, RS=0 */
 	lcd_send_serial(cmd & 0x0F);
 	lcd_send_serial((cmd >> 4) & 0x0F);
 	udelay(40);		/* the shortest command takes at least 40 us */
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 /* send data to the LCD panel in serial mode */
 static void lcd_write_data_s(int data)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	lcd_send_serial(0x5F);	/* R/W=W, RS=1 */
 	lcd_send_serial(data & 0x0F);
 	lcd_send_serial((data >> 4) & 0x0F);
 	udelay(40);		/* the shortest data takes at least 40 us */
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 /* send a command to the LCD panel in 8 bits parallel mode */
 static void lcd_write_cmd_p8(int cmd)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	/* present the data to the data port */
 	w_dtr(pprt, cmd);
 	udelay(20);	/* maintain the data during 20 us before the strobe */
@@ -802,13 +844,13 @@
 	set_ctrl_bits();
 
 	udelay(120);	/* the shortest command takes at least 120 us */
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 /* send data to the LCD panel in 8 bits parallel mode */
 static void lcd_write_data_p8(int data)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	/* present the data to the data port */
 	w_dtr(pprt, data);
 	udelay(20);	/* maintain the data during 20 us before the strobe */
@@ -824,27 +866,27 @@
 	set_ctrl_bits();
 
 	udelay(45);	/* the shortest data takes at least 45 us */
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 /* send a command to the TI LCD panel */
 static void lcd_write_cmd_tilcd(int cmd)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	/* present the data to the control port */
 	w_ctr(pprt, cmd);
 	udelay(60);
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 /* send data to the TI LCD panel */
 static void lcd_write_data_tilcd(int data)
 {
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	/* present the data to the data port */
 	w_dtr(pprt, data);
 	udelay(60);
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 }
 
 static void lcd_gotoxy(void)
@@ -877,14 +919,14 @@
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
 
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	for (pos = 0; pos < lcd_height * lcd_hwidth; pos++) {
 		lcd_send_serial(0x5F);	/* R/W=W, RS=1 */
 		lcd_send_serial(' ' & 0x0F);
 		lcd_send_serial((' ' >> 4) & 0x0F);
 		udelay(40);	/* the shortest data takes at least 40 us */
 	}
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
@@ -897,7 +939,7 @@
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
 
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	for (pos = 0; pos < lcd_height * lcd_hwidth; pos++) {
 		/* present the data to the data port */
 		w_dtr(pprt, ' ');
@@ -919,7 +961,7 @@
 		/* the shortest data takes at least 45 us */
 		udelay(45);
 	}
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
@@ -932,14 +974,14 @@
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
 
-	spin_lock(&pprt_lock);
+	lock_pprt();
 	for (pos = 0; pos < lcd_height * lcd_hwidth; pos++) {
 		/* present the data to the data port */
 		w_dtr(pprt, ' ');
 		udelay(60);
 	}
 
-	spin_unlock(&pprt_lock);
+	unlock_pprt();
 
 	lcd_addr_x = lcd_addr_y = 0;
 	lcd_gotoxy();
@@ -995,12 +1037,13 @@
 }
 
 /*
- * These are the file operation function for user access to /dev/lcd
+ * These are the file operation functions for user access to /dev/lcd
  * This function can also be called from inside the kernel, by
  * setting file and ppos to NULL.
  *
  */
 
+/* Called with port mutex held and port being available */
 static inline int handle_lcd_special_code(void)
 {
 	/* LCD special codes */
@@ -1044,13 +1087,10 @@
 		lcd_flags &= ~LCD_FLAG_L;
 		processed = 1;
 		break;
-	case '*':
-		/* flash back light using the keypad timer */
-		if (scan_timer.function != NULL) {
-			if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0))
-				lcd_backlight(1);
-			light_tempo = FLASH_LIGHT_TEMPO;
-		}
+	case '*':	/* Flash back light using the keypad timer */
+		lock_pprt();
+		backlight_flash = 1;
+		unlock_pprt();
 		processed = 1;
 		break;
 	case 'f':	/* Small Font */
@@ -1088,12 +1128,10 @@
 		processed = 1;
 		break;
 	case 'L':	/* shift display left */
-		lcd_left_shift++;
 		lcd_write_cmd(0x18);
 		processed = 1;
 		break;
 	case 'R':	/* shift display right */
-		lcd_left_shift--;
 		lcd_write_cmd(0x1C);
 		processed = 1;
 		break;
@@ -1109,7 +1147,6 @@
 	}
 	case 'I':	/* reinitialize display */
 		lcd_init_display();
-		lcd_left_shift = 0;
 		processed = 1;
 		break;
 	case 'G': {
@@ -1211,36 +1248,27 @@
 				      | ((lcd_flags & LCD_FLAG_F) ? 4 : 0)
 				      | ((lcd_flags & LCD_FLAG_N) ? 8 : 0));
 		/* check wether L flag was changed */
-		else if ((oldflags ^ lcd_flags) & (LCD_FLAG_L)) {
-			if (lcd_flags & (LCD_FLAG_L))
-				lcd_backlight(1);
-			else if (light_tempo == 0)
-				/* switch off the light only when the tempo
-				   lighting is gone */
-				lcd_backlight(0);
-		}
+		else if ((oldflags ^ lcd_flags) & (LCD_FLAG_L))
+			lcd_backlight((lcd_flags & LCD_FLAG_L) ? 1 : 0);
 	}
 
 	return processed;
 }
 
-static ssize_t lcd_write(struct file *file,
-			 const char *buf, size_t count, loff_t *ppos)
+
+static ssize_t lcd_write_unlocked(struct file *file,
+				  const char *buf, size_t count, loff_t *ppos)
 {
 	const char *tmp = buf;
 	char c;
 
 	for (; count-- > 0; (ppos ? (*ppos)++ : 0), ++tmp) {
-		if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
-			/* let's be a little nice with other processes
-			   that need some CPU */
-			schedule();
-
 		if (ppos == NULL && file == NULL)
 			/* let's not use get_user() from the kernel ! */
 			c = *tmp;
-		else if (get_user(c, tmp))
+		else if (get_user(c, tmp)) {
 			return -EFAULT;
+		}
 
 		/* first, we'll test if we're in escape mode */
 		if ((c != '\n') && lcd_escape_len >= 0) {
@@ -1334,29 +1362,67 @@
 	return tmp - buf;
 }
 
-static int lcd_open(struct inode *inode, struct file *file)
+
+static ssize_t lcd_write(struct file *file,
+			 const char *buf, size_t count, loff_t *ppos)
 {
-	if (lcd_open_cnt)
-		return -EBUSY;	/* open only once at a time */
+	ssize_t ret;
+
+	/* Protect access to the port and check that it's still available */
+	mutex_lock(&port_mutex);
 
-	if (file->f_mode & FMODE_READ)	/* device is write-only */
+	if (pprt == NULL) {
+		mutex_unlock(&port_mutex);
+		return -ENODEV;
+	}
+
+	ret = lcd_write_unlocked(file, buf, count, ppos);
+
+	mutex_unlock(&port_mutex);
+
+	return ret;
+}
+
+
+static int lcd_open(struct inode *inode, struct file *file)
+{
+	/* Device is write-only */
+	if (file->f_mode & FMODE_READ)
 		return -EPERM;
 
+	/* Only one open allowed at a time */
+	if (test_and_set_bit(0, &lcd_is_open))
+		return -EBUSY;
+
+	mutex_lock(&port_mutex);
+
+	/* Check for port still being available */
+	if (!pprt) {
+		mutex_unlock(&port_mutex);
+		return -ENODEV;
+	}
+
+	lcd_escape_len = -1;
+
+	/* Clear display if needed */
 	if (lcd_must_clear) {
 		lcd_clear_display();
 		lcd_must_clear = 0;
 	}
-	lcd_open_cnt++;
+
+	mutex_unlock(&port_mutex);
+
 	return nonseekable_open(inode, file);
 }
 
 static int lcd_release(struct inode *inode, struct file *file)
 {
-	lcd_open_cnt--;
+	clear_bit(0, &lcd_is_open);
 	return 0;
 }
 
 static const struct file_operations lcd_fops = {
+	.owner   = THIS_MODULE,
 	.write   = lcd_write,
 	.open    = lcd_open,
 	.release = lcd_release,
@@ -1369,15 +1435,29 @@
 	&lcd_fops
 };
 
+/*
+ * Print LCD messages, assumes caller has taken port mutex and checked that
+ * the port is available.
+ */
+static void panel_lcd_print_unlocked(char *s)
+{
+	lcd_write_unlocked(NULL, s, strlen(s), NULL);
+}
+
+
 /* public function usable from the kernel for any purpose */
-void panel_lcd_print(char *s)
+static void panel_lcd_print(char *s)
 {
-	if (lcd_enabled && lcd_initialized)
-		lcd_write(NULL, s, strlen(s), NULL);
+	mutex_lock(&port_mutex);
+
+	if (pprt)
+		panel_lcd_print_unlocked(s);
+
+	mutex_unlock(&port_mutex);
 }
 
-/* initialize the LCD driver */
-void lcd_init(void)
+/* Initialize LCD driver module globals */
+static void lcd_init_globals(void)
 {
 	switch (lcd_type) {
 	case LCD_TYPE_OLD:
@@ -1536,9 +1616,6 @@
 	else
 		lcd_char_conv = NULL;
 
-	if (lcd_bl_pin != PIN_NONE)
-		init_scan_timer();
-
 	pin_to_bits(lcd_e_pin, lcd_bits[LCD_PORT_D][LCD_BIT_E],
 		    lcd_bits[LCD_PORT_C][LCD_BIT_E]);
 	pin_to_bits(lcd_rs_pin, lcd_bits[LCD_PORT_D][LCD_BIT_RS],
@@ -1551,21 +1628,24 @@
 		    lcd_bits[LCD_PORT_C][LCD_BIT_CL]);
 	pin_to_bits(lcd_da_pin, lcd_bits[LCD_PORT_D][LCD_BIT_DA],
 		    lcd_bits[LCD_PORT_C][LCD_BIT_DA]);
+}
 
-	/* before this line, we must NOT send anything to the display.
-	 * Since lcd_init_display() needs to write data, we have to
-	 * enable mark the LCD initialized just before. */
-	lcd_initialized = 1;
+/* Do LCD panel initialization. Caller must hold port mutex */
+static void lcd_init(void)
+{
 	lcd_init_display();
 
+	lcd_escape_len = -1;
+
 	/* display a short message */
 #ifdef CONFIG_PANEL_CHANGE_MESSAGE
 #ifdef CONFIG_PANEL_BOOT_MESSAGE
-	panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
+	panel_lcd_print_unlocked("\x1b[Lc\x1b[Lb\x1b[L*"
+				 CONFIG_PANEL_BOOT_MESSAGE);
 #endif
 #else
-	panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\nPanel-"
-			PANEL_VERSION);
+	panel_lcd_print_unlocked("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE
+				 "\nPanel-" PANEL_VERSION);
 #endif
 	lcd_addr_x = lcd_addr_y = 0;
 	/* clear the display on the next device opening */
@@ -1573,6 +1653,14 @@
 	lcd_gotoxy();
 }
 
+
+/* Do LCD panel cleanup. Caller must hold port mutex */
+static void lcd_cleanup(void)
+{
+	panel_lcd_print_unlocked("\x0cLCD driver " PANEL_VERSION
+				 "\nstopped.\x1b[Lc\x1b[Lb\x1b[L-");
+}
+
 /*
  * These are the file operation function for user access to /dev/keypad
  */
@@ -1580,53 +1668,88 @@
 static ssize_t keypad_read(struct file *file,
 			   char *buf, size_t count, loff_t *ppos)
 {
+	static char key_buf[KEYPAD_BUFFER];
+	int i;
 
-	unsigned i = *ppos;
-	char *tmp = buf;
+	if (count == 0)
+		return 0;
+
+	/*
+	 * In case data is available in the global buffer, copy it to our local
+	 * buffer. This is needed since we can't copy to the user buffer while
+	 * holding the spinlock. Our local buffer can be static since there can
+	 * only be one opener of the device.
+	 *
+	 * In case there is no data available, we put the process on a wait
+	 * queue. It will be woken up when data arrives.
+	 */
+	spin_lock_bh(&keypad_lock);
+
+	while (keypad_buflen == 0) {
+		spin_unlock_bh(&keypad_lock);
 
-	if (keypad_buflen == 0) {
 		if (file->f_flags & O_NONBLOCK)
 			return -EAGAIN;
 
-		interruptible_sleep_on(&keypad_read_wait);
-		if (signal_pending(current))
-			return -EINTR;
+		if (wait_event_interruptible(keypad_read_wait,
+					     (keypad_buflen != 0)))
+			return -ERESTARTSYS;
+
+		spin_lock_bh(&keypad_lock);
 	}
 
-	for (; count-- > 0 && (keypad_buflen > 0);
-	     ++i, ++tmp, --keypad_buflen) {
-		put_user(keypad_buffer[keypad_start], tmp);
+	for (i = 0; (size_t) i < count && i < keypad_buflen; i++) {
+		key_buf[i] = keypad_buffer[keypad_start];
 		keypad_start = (keypad_start + 1) % KEYPAD_BUFFER;
 	}
-	*ppos = i;
+	keypad_buflen -= i;
 
-	return tmp - buf;
+	spin_unlock_bh(&keypad_lock);
+
+	if (copy_to_user(buf, key_buf, i))
+		return -EFAULT;
+
+	(*ppos) += (loff_t) i;
+
+	return (ssize_t) i;
 }
 
 static int keypad_open(struct inode *inode, struct file *file)
 {
-
-	if (keypad_open_cnt)
-		return -EBUSY;	/* open only once at a time */
+	int r;
 
 	if (file->f_mode & FMODE_WRITE)	/* device is read-only */
 		return -EPERM;
 
-	keypad_buflen = 0;	/* flush the buffer on opening */
-	keypad_open_cnt++;
-	return 0;
+	spin_lock_bh(&keypad_lock);
+
+	if (keypad_open_cnt)
+		r = -EBUSY;		/* open only once at a time */
+	else {
+		keypad_open_cnt++;
+		keypad_buflen = 0;	/* flush the buffer on opening */
+		keypad_start = 0;
+		r = 0;
+	}
+
+	spin_unlock_bh(&keypad_lock);
+
+	return r;
 }
 
 static int keypad_release(struct inode *inode, struct file *file)
 {
+	spin_lock_bh(&keypad_lock);
 	keypad_open_cnt--;
+	spin_unlock_bh(&keypad_lock);
 	return 0;
 }
 
 static const struct file_operations keypad_fops = {
-	.read    = keypad_read,		/* read */
-	.open    = keypad_open,		/* open */
-	.release = keypad_release,	/* close */
+	.owner   = THIS_MODULE,
+	.read    = keypad_read,
+	.open    = keypad_open,
+	.release = keypad_release,
 	.llseek  = default_llseek,
 };
 
@@ -1638,17 +1761,23 @@
 
 static void keypad_send_key(char *string, int max_len)
 {
-	if (init_in_progress)
-		return;
+	int wake = 0;
 
 	/* send the key to the device only if a process is attached to it. */
+	spin_lock(&keypad_lock);
+
 	if (keypad_open_cnt > 0) {
 		while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) {
 			keypad_buffer[(keypad_start + keypad_buflen++) %
 				      KEYPAD_BUFFER] = *string++;
 		}
-		wake_up_interruptible(&keypad_read_wait);
+		wake = 1;
 	}
+
+	spin_unlock(&keypad_lock);
+
+	if (wake)
+		wake_up_interruptible(&keypad_read_wait);
 }
 
 /* this function scans all the bits involving at least one logical signal,
@@ -1660,6 +1789,8 @@
  * reads will be kept as they previously were in their logical form (phys_prev).
  * A signal which has just switched will have a 1 in
  * (phys_read ^ phys_read_prev).
+ *
+ * Assumes caller holds port spinlock.
  */
 static void phys_scan_contacts(void)
 {
@@ -1838,9 +1969,8 @@
 	struct logical_input *input;
 
 #if 0
-	printk(KERN_DEBUG
-	       "entering panel_process_inputs with pp=%016Lx & pc=%016Lx\n",
-	       phys_prev, phys_curr);
+	pr_debug("entering panel_process_inputs with pp=%016Lx & pc=%016Lx\n",
+		 phys_prev, phys_curr);
 #endif
 
 	keypressed = 0;
@@ -1889,45 +2019,64 @@
 
 static void panel_scan_timer(void)
 {
-	if (keypad_enabled && keypad_initialized) {
-		if (spin_trylock(&pprt_lock)) {
-			phys_scan_contacts();
-
-			/* no need for the parport anymore */
-			spin_unlock(&pprt_lock);
-		}
+	/* If keypad available scan keys and send to waiting process */
+	if (keypad_initialized) {
+		spin_lock(&pprt_lock);
+		phys_scan_contacts();
+		spin_unlock(&pprt_lock);
 
 		if (!inputs_stable || phys_curr != phys_prev)
 			panel_process_inputs();
 	}
 
-	if (lcd_enabled && lcd_initialized) {
-		if (keypressed) {
-			if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0))
-				lcd_backlight(1);
+	/* Turn backlight on/off if available */
+	if (lcd_enabled && lcd_bl_pin != PIN_NONE) {
+		spin_lock(&pprt_lock);
+
+		if (backlight_flash || keypressed) {
+			backlight_flash = 0;
+			if (light_tempo == 0 && !backlight_on)
+				lcd_backlight_unlocked(1);
 			light_tempo = FLASH_LIGHT_TEMPO;
 		} else if (light_tempo > 0) {
 			light_tempo--;
-			if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0))
-				lcd_backlight(0);
+			if (light_tempo == 0 && backlight_on &&
+			    !force_backlight_on) {
+				lcd_backlight_unlocked(0);
+			}
 		}
+
+		spin_unlock(&pprt_lock);
 	}
 
 	mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME);
 }
 
-static void init_scan_timer(void)
+/* Start scan timer, assuming timer is not running */
+static void start_scan_timer(void)
 {
-	if (scan_timer.function != NULL)
-		return;		/* already started */
+	light_tempo = 0;
 
 	init_timer(&scan_timer);
 	scan_timer.expires = jiffies + INPUT_POLL_TIME;
 	scan_timer.data = 0;
-	scan_timer.function = (void *)&panel_scan_timer;
+	scan_timer.function = (void *) &panel_scan_timer;
 	add_timer(&scan_timer);
+	timer_running = 1;
+}
+
+/*
+ * Stop scan timer and wait for it to finish. Assumes that timer is running.
+ * Note that we don't bother waking up any processes in the wait queue, since
+ * we shouldn't have come here with dangling opens to the keypad.
+ */
+static void stop_scan_timer(void)
+{
+	del_timer_sync(&scan_timer);
+	timer_running = 0;
 }
 
+
 /* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits.
  * if <omask> or <imask> are non-null, they will be or'ed with the bits
  * corresponding to out and in bits respectively.
@@ -1979,22 +2128,22 @@
 
 /* tries to bind a key to the signal name <name>. The key will send the
  * strings <press>, <repeat>, <release> for these respective events.
- * Returns the pointer to the new key if ok, NULL if the key could not be bound.
+ * Returns 0 on success or negative error code. Updates the logical_inputs,
+ * scan_mask_i and scan_mask_o globals.
  */
-static struct logical_input *panel_bind_key(char *name, char *press,
-					    char *repeat, char *release)
+static int panel_bind_key(char *name, char *press, char *repeat, char *release)
 {
 	struct logical_input *key;
 
 	key = kzalloc(sizeof(struct logical_input), GFP_KERNEL);
 	if (!key) {
-		printk(KERN_ERR "panel: not enough memory\n");
-		return NULL;
+		pr_err("panel: not enough memory\n");
+		return -ENOMEM;
 	}
 	if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i,
 			     &scan_mask_o)) {
 		kfree(key);
-		return NULL;
+		return -EINVAL;
 	}
 
 	key->type = INPUT_TYPE_KBD;
@@ -2003,15 +2152,15 @@
 	key->fall_time = 1;
 
 #if 0
-	printk(KERN_DEBUG "bind: <%s> : m=%016Lx v=%016Lx\n", name, key->mask,
-	       key->value);
+	pr_debug("bind: <%s> : m=%016Lx v=%016Lx\n", name, key->mask,
+		 key->value);
 #endif
 	strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str));
 	strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str));
 	strncpy(key->u.kbd.release_str, release,
 		sizeof(key->u.kbd.release_str));
 	list_add(&key->list, &logical_inputs);
-	return key;
+	return 0;
 }
 
 #if 0
@@ -2031,7 +2180,7 @@
 
 	callback = kmalloc(sizeof(struct logical_input), GFP_KERNEL);
 	if (!callback) {
-		printk(KERN_ERR "panel: not enough memory\n");
+		pr_err("panel: not enough memory\n");
 		return NULL;
 	}
 	memset(callback, 0, sizeof(struct logical_input));
@@ -2052,23 +2201,46 @@
 }
 #endif
 
-static void keypad_init(void)
+/* Free memory for keypad variables. Assumes that timer function doesn't run */
+static void keypad_cleanup(void)
+{
+	struct logical_input *p, *next;
+
+	list_for_each_entry_safe(p, next, &logical_inputs, list) {
+		list_del(&p->list);
+		kfree(p);
+	}
+
+	keypad_initialized = 0;
+}
+
+
+/*
+ * Allocate and initialize keypad variables. Assumes that timer function
+ * doesn't run. Returns 0 on success or negative error code.
+ */
+static int keypad_init(void)
 {
 	int keynum;
-	init_waitqueue_head(&keypad_read_wait);
-	keypad_buflen = 0;	/* flushes any eventual noisy keystroke */
+	int r;
 
-	/* Let's create all known keys */
+	scan_mask_o = scan_mask_i = 0;
 
+	/* Let's create all known keys */
 	for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) {
-		panel_bind_key(keypad_profile[keynum][0],
-			       keypad_profile[keynum][1],
-			       keypad_profile[keynum][2],
-			       keypad_profile[keynum][3]);
+		r = panel_bind_key(keypad_profile[keynum][0],
+				   keypad_profile[keynum][1],
+				   keypad_profile[keynum][2],
+				   keypad_profile[keynum][3]);
+		if (r < 0) {
+			keypad_cleanup();
+			return r;
+		}
+
 	}
 
-	init_scan_timer();
 	keypad_initialized = 1;
+	return 0;
 }
 
 /**************************************************/
@@ -2078,7 +2250,7 @@
 static int panel_notify_sys(struct notifier_block *this, unsigned long code,
 			    void *unused)
 {
-	if (lcd_enabled && lcd_initialized) {
+	if (lcd_enabled) {
 		switch (code) {
 		case SYS_DOWN:
 			panel_lcd_print
@@ -2089,7 +2261,8 @@
 			    ("\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
 			break;
 		case SYS_POWER_OFF:
-			panel_lcd_print("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
+			panel_lcd_print
+			    ("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
 			break;
 		default:
 			break;
@@ -2106,85 +2279,141 @@
 
 static void panel_attach(struct parport *port)
 {
+	struct pardevice *portdev;
+	int r;
+
+	/*
+	 * If this is the port we wanted, attempt to claim the port for
+	 * ourselves.
+	 */
 	if (port->number != parport)
 		return;
 
-	if (pprt) {
-		printk(KERN_ERR
-		       "panel_attach(): port->number=%d parport=%d, "
-		       "already registered !\n",
-		       port->number, parport);
+	portdev = parport_register_device(port, "panel", NULL, NULL, NULL, 0,
+					  NULL);
+	if (portdev == NULL) {
+		pr_err("Panel: Failed to register with parport%d.\n", parport);
 		return;
 	}
 
-	pprt = parport_register_device(port, "panel", NULL, NULL,  /* pf, kf */
-				       NULL,
-				       /*PARPORT_DEV_EXCL */
-				       0, (void *)&pprt);
-	if (pprt == NULL) {
-		pr_err("panel_attach(): port->number=%d parport=%d, "
-		       "parport_register_device() failed\n",
-		       port->number, parport);
+	if (parport_claim(portdev)) {
+		pr_err("Panel: Failed to claim parport%d.\n", parport);
+		parport_unregister_device(portdev);
 		return;
 	}
 
-	if (parport_claim(pprt)) {
-		printk(KERN_ERR
-		       "Panel: could not claim access to parport%d. "
-		       "Aborting.\n", parport);
-		goto err_unreg_device;
+	/*
+	 * Port successfully owned, update global state and initialize LCD if
+	 * enabled.
+	 */
+	mutex_lock(&port_mutex);
+
+	if (pprt) {
+		mutex_unlock(&port_mutex);
+		pr_err("Panel: Parport%d already registered.\n", parport);
+		parport_release(portdev);
+		parport_unregister_device(portdev);
+		return;
 	}
 
-	/* must init LCD first, just in case an IRQ from the keypad is
-	 * generated at keypad init
-	 */
-	if (lcd_enabled) {
+	pprt = portdev;
+
+	if (lcd_enabled)
 		lcd_init();
-		if (misc_register(&lcd_dev))
-			goto err_unreg_device;
-	}
 
+	mutex_unlock(&port_mutex);
+
+	pr_info("Panel: registered on parport%d (io=0x%lx).\n", parport,
+		port->base);
+
+	/*
+	 * Port and LCD initialized. After this point we keep ownership of the
+	 * port in all cases. It will only be released in detach.
+	 */
+
+	/*
+	 * Initialize keypad and timer. Start timer only if keypad is enabled
+	 * or an LCD with backlight support is enabled.
+	 *
+	 * We can do this without any locking since they only need to be
+	 * synchronized with detach, and parport makes sure that detach is not
+	 * called while we are in attach.
+	 */
 	if (keypad_enabled) {
-		keypad_init();
-		if (misc_register(&keypad_dev))
-			goto err_lcd_unreg;
+		r = keypad_init();
+		if (r < 0)
+			pr_err("Panel: error %d initializing keypad.\n", r);
 	}
-	return;
 
-err_lcd_unreg:
-	if (lcd_enabled)
-		misc_deregister(&lcd_dev);
-err_unreg_device:
-	parport_unregister_device(pprt);
-	pprt = NULL;
+	if (keypad_initialized || (lcd_enabled && (lcd_bl_pin != PIN_NONE)))
+		start_scan_timer();
+
+	/* Finally attempt to register misc devices for LCD/keypad */
+	if (lcd_enabled) {
+		r = misc_register(&lcd_dev);
+		if (r < 0)
+			pr_err("Panel: error %d registering LCD dev.\n", r);
+	}
+
+	if (keypad_initialized) {
+		r = misc_register(&keypad_dev);
+		if (r < 0)
+			pr_err("Panel: error %d registering keypad dev.\n", r);
+	}
 }
 
 static void panel_detach(struct parport *port)
 {
+	struct pardevice *portdev;
+
+	/*
+	 * Quick check for our port. Note that it is safe to access pprt
+	 * without a lock in this case since it can not change under us
+	 * (parport synchronizes multiple calls to attach/detach).
+	 */
 	if (port->number != parport)
 		return;
 
-	if (!pprt) {
-		printk(KERN_ERR
-		       "panel_detach(): port->number=%d parport=%d, "
-		       "nothing to unregister.\n",
-		       port->number, parport);
+	if (!pprt || pprt->port != port) {
+		pr_err("Panel: Nothing to unregister for parport%d.\n",
+		       parport);
 		return;
 	}
 
-	if (keypad_enabled && keypad_initialized) {
+	/* Detach is for our port. Stop timer and free keypad variables. */
+	if (timer_running)
+		stop_scan_timer();
+
+	if (keypad_initialized)
+		keypad_cleanup();
+
+	/* Unregister misc devices if registered */
+	if (!list_empty(&keypad_dev.list)) {
 		misc_deregister(&keypad_dev);
-		keypad_initialized = 0;
+		INIT_LIST_HEAD(&keypad_dev.list);
 	}
 
-	if (lcd_enabled && lcd_initialized) {
+	if (!list_empty(&lcd_dev.list)) {
 		misc_deregister(&lcd_dev);
-		lcd_initialized = 0;
+		INIT_LIST_HEAD(&lcd_dev.list);
 	}
 
-	parport_release(pprt);
-	parport_unregister_device(pprt);
+	/* Finally release the port */
+	mutex_lock(&port_mutex);
+
+	if (lcd_enabled)
+		lcd_cleanup();
+
+	portdev = pprt;
 	pprt = NULL;
+
+	mutex_unlock(&port_mutex);
+
+	parport_release(portdev);
+	parport_unregister_device(portdev);
+
+	pr_info("Panel: deregistered from parport%d (io=0x%lx).\n", parport,
+		port->base);
 }
 
 static struct parport_driver panel_driver = {
@@ -2193,8 +2422,12 @@
 	.detach = panel_detach,
 };
 
-/* init function */
-int panel_init(void)
+/*
+ * Initialize global variables from configuration. These are initialized when
+ * the module is loaded and remain constant until panel_cleanup is called when
+ * the module is unloaded.
+ */
+static void panel_init_globals(void)
 {
 	/* for backwards compatibility */
 	if (keypad_type < 0)
@@ -2269,78 +2502,62 @@
 	case KEYPAD_TYPE_NEXCOM:
 		keypad_profile = nexcom_keypad_profile;
 		break;
-	default:
-		keypad_profile = NULL;
-		break;
 	}
 
-	/* tells various subsystems about the fact that we are initializing */
-	init_in_progress = 1;
+	lcd_init_globals();
+}
 
-	if (parport_register_driver(&panel_driver)) {
-		printk(KERN_ERR
-		       "Panel: could not register with parport. Aborting.\n");
-		return -EIO;
-	}
+static int __init panel_init_module(void)
+{
+	int r;
+
+	panel_init_globals();
 
+	/* Used to check with list_empty if devices were registered */
+	INIT_LIST_HEAD(&lcd_dev.list);
+	INIT_LIST_HEAD(&keypad_dev.list);
+
+	/* We have nothing to do if neither lcd nor keypad enabled */
 	if (!lcd_enabled && !keypad_enabled) {
-		/* no device enabled, let's release the parport */
-		if (pprt) {
-			parport_release(pprt);
-			parport_unregister_device(pprt);
-			pprt = NULL;
-		}
-		parport_unregister_driver(&panel_driver);
-		printk(KERN_ERR "Panel driver version " PANEL_VERSION
-		       " disabled.\n");
+		pr_err("Panel driver version " PANEL_VERSION " disabled.\n");
 		return -ENODEV;
 	}
 
+	/*
+	 * Register with parport. From this point the code must be prepared to
+	 * handle attach/detach. parport notifies us immediately about existing
+	 * ports.
+	 */
+	r = parport_register_driver(&panel_driver);
+	if (r) {
+		pr_err("Panel: could not register with parport. Aborting.\n");
+		return r;
+	}
+
 	register_reboot_notifier(&panel_notifier);
 
-	if (pprt)
-		printk(KERN_INFO "Panel driver version " PANEL_VERSION
-		       " registered on parport%d (io=0x%lx).\n", parport,
-		       pprt->port->base);
-	else
-		printk(KERN_INFO "Panel driver version " PANEL_VERSION
-		       " not yet registered\n");
-	/* tells various subsystems about the fact that initialization
-	   is finished */
-	init_in_progress = 0;
-	return 0;
-}
+	/*
+	 * We might already have found our port (the typical case). Inform the
+	 * user if the port was not yet found. Note that it is safe to access
+	 * the port variable without the lock in this special case.
+	 */
+	pr_info("Panel driver version " PANEL_VERSION " loaded.\n");
+	if (!pprt)
+		pr_info("Waiting for parallel port to become available.\n");
 
-static int __init panel_init_module(void)
-{
-	return panel_init();
+	return 0;
 }
 
 static void __exit panel_cleanup_module(void)
 {
 	unregister_reboot_notifier(&panel_notifier);
 
-	if (scan_timer.function != NULL)
-		del_timer(&scan_timer);
-
-	if (pprt != NULL) {
-		if (keypad_enabled) {
-			misc_deregister(&keypad_dev);
-			keypad_initialized = 0;
-		}
-
-		if (lcd_enabled) {
-			panel_lcd_print("\x0cLCD driver " PANEL_VERSION
-					"\nunloaded.\x1b[Lc\x1b[Lb\x1b[L-");
-			misc_deregister(&lcd_dev);
-			lcd_initialized = 0;
-		}
-
-		/* TODO: free all input signals */
-		parport_release(pprt);
-		parport_unregister_device(pprt);
-		pprt = NULL;
-	}
+	/*
+	 * The unregister function makes sure that detach is called for all
+	 * ports, and that detach has finished running before returning here.
+	 * Therefore there is not much for us to do here since detach handles
+	 * all cleanup for the port.
+	 */
 	parport_unregister_driver(&panel_driver);
 }
 
_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/devel

[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux