drm/i915: cope with large i2c transfers
authorDmitry Torokhov <dmitry.torokhov@gmail.com>
Tue, 21 Apr 2015 16:49:11 +0000 (09:49 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 6 May 2015 19:56:27 +0000 (21:56 +0200)
commit 9535c4757b881e06fae72a857485ad57c422b8d2 upstream.

The hardware, according to the specs, is limited to 256 byte transfers,
and current driver has no protections in case users attempt to do larger
transfers. The code will just stomp over status register and mayhem
ensues.

Let's split larger transfers into digestable chunks. Doing this allows
Atmel MXT driver on Pixel 1 function properly (it hasn't since commit
9d8dc3e529a19e427fd379118acd132520935c5d "Input: atmel_mxt_ts -
implement T44 message handling" which tries to consume multiple
touchscreen/touchpad reports in a single transaction).

Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/gpu/drm/i915/i915_reg.h
drivers/gpu/drm/i915/intel_i2c.c

index 7695b5dd9d2d3d95106d2658b175434e691db0d3..35287ab445cd9ef2a88b410c5ed1566b335e08c0 100644 (file)
 #define   GMBUS_CYCLE_INDEX    (2<<25)
 #define   GMBUS_CYCLE_STOP     (4<<25)
 #define   GMBUS_BYTE_COUNT_SHIFT 16
+#define   GMBUS_BYTE_COUNT_MAX   256U
 #define   GMBUS_SLAVE_INDEX_SHIFT 8
 #define   GMBUS_SLAVE_ADDR_SHIFT 1
 #define   GMBUS_SLAVE_READ     (1<<0)
index 639fe192997cdecb602c789f97045aad61328249..4a21e13cc58c15461191bb5980ee7de68e5a7f79 100644 (file)
@@ -276,18 +276,17 @@ gmbus_wait_idle(struct drm_i915_private *dev_priv)
 }
 
 static int
-gmbus_xfer_read(struct drm_i915_private *dev_priv, struct i2c_msg *msg,
-               u32 gmbus1_index)
+gmbus_xfer_read_chunk(struct drm_i915_private *dev_priv,
+                     unsigned short addr, u8 *buf, unsigned int len,
+                     u32 gmbus1_index)
 {
        int reg_offset = dev_priv->gpio_mmio_base;
-       u16 len = msg->len;
-       u8 *buf = msg->buf;
 
        I915_WRITE(GMBUS1 + reg_offset,
                   gmbus1_index |
                   GMBUS_CYCLE_WAIT |
                   (len << GMBUS_BYTE_COUNT_SHIFT) |
-                  (msg->addr << GMBUS_SLAVE_ADDR_SHIFT) |
+                  (addr << GMBUS_SLAVE_ADDR_SHIFT) |
                   GMBUS_SLAVE_READ | GMBUS_SW_RDY);
        while (len) {
                int ret;
@@ -309,11 +308,35 @@ gmbus_xfer_read(struct drm_i915_private *dev_priv, struct i2c_msg *msg,
 }
 
 static int
-gmbus_xfer_write(struct drm_i915_private *dev_priv, struct i2c_msg *msg)
+gmbus_xfer_read(struct drm_i915_private *dev_priv, struct i2c_msg *msg,
+               u32 gmbus1_index)
 {
-       int reg_offset = dev_priv->gpio_mmio_base;
-       u16 len = msg->len;
        u8 *buf = msg->buf;
+       unsigned int rx_size = msg->len;
+       unsigned int len;
+       int ret;
+
+       do {
+               len = min(rx_size, GMBUS_BYTE_COUNT_MAX);
+
+               ret = gmbus_xfer_read_chunk(dev_priv, msg->addr,
+                                           buf, len, gmbus1_index);
+               if (ret)
+                       return ret;
+
+               rx_size -= len;
+               buf += len;
+       } while (rx_size != 0);
+
+       return 0;
+}
+
+static int
+gmbus_xfer_write_chunk(struct drm_i915_private *dev_priv,
+                      unsigned short addr, u8 *buf, unsigned int len)
+{
+       int reg_offset = dev_priv->gpio_mmio_base;
+       unsigned int chunk_size = len;
        u32 val, loop;
 
        val = loop = 0;
@@ -325,8 +348,8 @@ gmbus_xfer_write(struct drm_i915_private *dev_priv, struct i2c_msg *msg)
        I915_WRITE(GMBUS3 + reg_offset, val);
        I915_WRITE(GMBUS1 + reg_offset,
                   GMBUS_CYCLE_WAIT |
-                  (msg->len << GMBUS_BYTE_COUNT_SHIFT) |
-                  (msg->addr << GMBUS_SLAVE_ADDR_SHIFT) |
+                  (chunk_size << GMBUS_BYTE_COUNT_SHIFT) |
+                  (addr << GMBUS_SLAVE_ADDR_SHIFT) |
                   GMBUS_SLAVE_WRITE | GMBUS_SW_RDY);
        while (len) {
                int ret;
@@ -343,6 +366,29 @@ gmbus_xfer_write(struct drm_i915_private *dev_priv, struct i2c_msg *msg)
                if (ret)
                        return ret;
        }
+
+       return 0;
+}
+
+static int
+gmbus_xfer_write(struct drm_i915_private *dev_priv, struct i2c_msg *msg)
+{
+       u8 *buf = msg->buf;
+       unsigned int tx_size = msg->len;
+       unsigned int len;
+       int ret;
+
+       do {
+               len = min(tx_size, GMBUS_BYTE_COUNT_MAX);
+
+               ret = gmbus_xfer_write_chunk(dev_priv, msg->addr, buf, len);
+               if (ret)
+                       return ret;
+
+               buf += len;
+               tx_size -= len;
+       } while (tx_size != 0);
+
        return 0;
 }