]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
fsi: core: Fix small accesses and unaligned offsets via sysfs
authorAndrew Jeffery <andrew@aj.id.au>
Fri, 8 Nov 2019 05:19:39 +0000 (15:49 +1030)
committerKhalid Elmously <khalid.elmously@canonical.com>
Wed, 29 Jan 2020 04:45:21 +0000 (23:45 -0500)
BugLink: https://bugs.launchpad.net/bugs/1859712
[ Upstream commit 9f4c2b516b4f031e3cd0e45957f4150b3c1a083d ]

Subtracting the offset delta from four-byte alignment lead to wrapping
of the requested length where `count` is less than `off`. Generalise the
length handling to enable and optimise aligned access sizes for all
offset and size combinations. The new formula produces the following
results for given offset and count values:

    offset  count | length
    --------------+-------
    0       1     | 1
    0       2     | 2
    0       3     | 2
    0       4     | 4
    0       5     | 4
    1       1     | 1
    1       2     | 1
    1       3     | 1
    1       4     | 1
    1       5     | 1
    2       1     | 1
    2       2     | 2
    2       3     | 2
    2       4     | 2
    2       5     | 2
    3       1     | 1
    3       2     | 1
    3       3     | 1
    3       4     | 1
    3       5     | 1

We might need something like this for the cfam chardevs as well, for
example we don't currently implement any alignment restrictions /
handling in the hardware master driver.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Joel Stanley <joel@jms.id.au>
Link: https://lore.kernel.org/r/20191108051945.7109-6-joel@jms.id.au
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Khalid Elmously <khalid.elmously@canonical.com>
drivers/fsi/fsi-core.c

index e318bf8c623c66e33201d1592dba5a9b86f762c7..6691529eb5b765d9139acd1460fba87eb6d888c8 100644 (file)
@@ -419,6 +419,31 @@ static int fsi_slave_scan(struct fsi_slave *slave)
        return 0;
 }
 
+static unsigned long aligned_access_size(size_t offset, size_t count)
+{
+       unsigned long offset_unit, count_unit;
+
+       /* Criteria:
+        *
+        * 1. Access size must be less than or equal to the maximum access
+        *    width or the highest power-of-two factor of offset
+        * 2. Access size must be less than or equal to the amount specified by
+        *    count
+        *
+        * The access width is optimal if we can calculate 1 to be strictly
+        * equal while still satisfying 2.
+        */
+
+       /* Find 1 by the bottom bit of offset (with a 4 byte access cap) */
+       offset_unit = BIT(__builtin_ctzl(offset | 4));
+
+       /* Find 2 by the top bit of count */
+       count_unit = BIT(8 * sizeof(unsigned long) - 1 - __builtin_clzl(count));
+
+       /* Constrain the maximum access width to the minimum of both criteria */
+       return BIT(__builtin_ctzl(offset_unit | count_unit));
+}
+
 static ssize_t fsi_slave_sysfs_raw_read(struct file *file,
                struct kobject *kobj, struct bin_attribute *attr, char *buf,
                loff_t off, size_t count)
@@ -434,8 +459,7 @@ static ssize_t fsi_slave_sysfs_raw_read(struct file *file,
                return -EINVAL;
 
        for (total_len = 0; total_len < count; total_len += read_len) {
-               read_len = min_t(size_t, count, 4);
-               read_len -= off & 0x3;
+               read_len = aligned_access_size(off, count - total_len);
 
                rc = fsi_slave_read(slave, off, buf + total_len, read_len);
                if (rc)
@@ -462,8 +486,7 @@ static ssize_t fsi_slave_sysfs_raw_write(struct file *file,
                return -EINVAL;
 
        for (total_len = 0; total_len < count; total_len += write_len) {
-               write_len = min_t(size_t, count, 4);
-               write_len -= off & 0x3;
+               write_len = aligned_access_size(off, count - total_len);
 
                rc = fsi_slave_write(slave, off, buf + total_len, write_len);
                if (rc)