syzbot


ID Workflow Result Correct Bug Created Started Finished Revision Error
f98dc19b-0812-4363-9e67-dcaed89adf88 patching KASAN: stack-out-of-bounds Read in ft260_smbus_write 2026/01/29 18:54 2026/01/29 18:54 2026/01/29 19:45 9f91b6e4a37d1ff41db8959be87cce846b2b5b47


KernelBranch:
master


KernelCommit:
63804fed149a6750ffd28610c5c1c98cce6bd377


KernelRepo:
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git


PatchDescription:
HID: ft260: fix stack OOB in ft260_smbus_xfer

The ft260_smbus_xfer() function handles I2C SMBus transfers. When
processing I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA
commands, it uses data->block[0] as the length of the data to be
transferred. This value comes from user space and is not validated
against the maximum allowed block size (I2C_SMBUS_BLOCK_MAX).

If a malicious or buggy user space program provides a length larger
than I2C_SMBUS_BLOCK_MAX (32), it leads to an out-of-bounds read
from the stack-allocated union i2c_smbus_data structure in
ft260_smbus_write().

This issue is detected by KASAN:
  BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0
  Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

Add a check to ensure that data->block[0] does not exceed
I2C_SMBUS_BLOCK_MAX before processing the block transfer.


PatchDiff:
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);


Recipients:
[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]

Crash report:
==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90003427d81 by task syz.2.65/6119

CPU: 0 UID: 0 PID: 6119 Comm: syz.2.65 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xcd/0x630 mm/kasan/report.c:482
 kasan_report+0xe0/0x110 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:194 [inline]
 kasan_check_range+0x100/0x1b0 mm/kasan/generic.c:200
 __asan_memcpy+0x23/0x60 mm/kasan/shadow.c:105
 ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x22c/0x640 drivers/hid/hid-ft260.c:736
 __i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:591 [inline]
 __i2c_smbus_xfer+0x4f0/0xf60 drivers/i2c/i2c-core-smbus.c:554
 i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:546 [inline]
 i2c_smbus_xfer+0x200/0x3c0 drivers/i2c/i2c-core-smbus.c:536
 i2cdev_ioctl_smbus+0x237/0x990 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x361/0x840 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f88dd98f749
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffeebbe43a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f88ddbe5fa0 RCX: 00007f88dd98f749
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00007f88dda13f91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f88ddbe5fa0 R14: 00007f88ddbe5fa0 R15: 0000000000000003
 </TASK>

The buggy address belongs to stack of task syz.2.65/6119
 and is located at offset 49 in frame:
 i2cdev_ioctl_smbus+0x0/0x990 drivers/i2c/i2c-dev.c:632

This frame has 1 object:
 [48, 82) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90003420000 allocated at kernel_clone+0xfc/0x910 kernel/fork.c:2651
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x22397
memcg:ffff888074f11282
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000088e5c8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff ffff888074f11282
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5928, tgid 5928 (syz-executor), ts 80598654650, free_ts 80595057505
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x1af/0x220 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0xd0b/0x31a0 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x25f/0x2430 mm/page_alloc.c:5240
 alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0x131/0x390 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0xe6c/0x16b0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct kernel/fork.c:923 [inline]
 copy_process+0x619/0x7430 kernel/fork.c:2052
 kernel_clone+0xfc/0x910 kernel/fork.c:2651
 __do_sys_clone+0xce/0x120 kernel/fork.c:2792
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5928 tgid 5928 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0x7df/0x1170 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x130/0x170 mm/slub.c:3886
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x4c/0xf0 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 __kmalloc_cache_node_noprof+0x299/0x830 mm/slub.c:5784
 kmalloc_node_noprof include/linux/slab.h:983 [inline]
 __get_vm_area_node+0x101/0x330 mm/vmalloc.c:3208
 __vmalloc_node_range_noprof+0x247/0x16b0 mm/vmalloc.c:4011
 __vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
 xt_counters_alloc+0x4c/0x70 net/netfilter/x_tables.c:1381
 __do_replace+0x97/0x9e0 net/ipv4/netfilter/ip_tables.c:1046
 do_replace net/ipv4/netfilter/ip_tables.c:1141 [inline]
 do_ipt_set_ctl+0x93b/0xc40 net/ipv4/netfilter/ip_tables.c:1635
 nf_setsockopt+0x8d/0xf0 net/netfilter/nf_sockopt.c:101
 ip_setsockopt+0xcb/0xf0 net/ipv4/ip_sockglue.c:1424
 tcp_setsockopt+0xa7/0x100 net/ipv4/tcp.c:4164
 do_sock_setsockopt+0xf3/0x1d0 net/socket.c:2322

Memory state around the buggy address:
 ffffc90003427c80: f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90003427d00: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1
>ffffc90003427d80: 00 00 00 00 02 f3 f3 f3 f3 f3 00 00 00 00 00 00
                               ^
 ffffc90003427e00: 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1 04 f2 00 00
 ffffc90003427e80: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
==================================================================

Trajectory:
Seq Timestamp Type Name Duration
0/0 2026/01/29 18:54 flow patching
51m Results:
map[KernelBranch:master KernelCommit:63804fed149a6750ffd28610c5c1c98cce6bd377 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git PatchDescription:HID: ft260: fix stack OOB in ft260_smbus_xfer

The ft260_smbus_xfer() function handles I2C SMBus transfers. When
processing I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA
commands, it uses data->block[0] as the length of the data to be
transferred. This value comes from user space and is not validated
against the maximum allowed block size (I2C_SMBUS_BLOCK_MAX).

If a malicious or buggy user space program provides a length larger
than I2C_SMBUS_BLOCK_MAX (32), it leads to an out-of-bounds read
from the stack-allocated union i2c_smbus_data structure in
ft260_smbus_write().

This issue is detected by KASAN:
  BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0
  Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

Add a check to ensure that data->block[0] does not exceed
I2C_SMBUS_BLOCK_MAX before processing the block transfer. PatchDiff:diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
 Recipients:[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]]

1/1 2026/01/29 18:54 action base-commit-picker
0m Results:
map[KernelBranch:master KernelCommit:63804fed149a6750ffd28610c5c1c98cce6bd377 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git]

2/1 2026/01/29 18:54 action kernel-checkouter
0m Results:
map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/8c64ca34582568f44cbf475e4f60a6f0b9d98e80]

3/1 2026/01/29 18:54 action kernel-builder
6m Results:
map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/075051b8b4ae86406308820d3c12d14af032f80b]

4/1 2026/01/29 19:00 action crash-reproducer
1m Results:
map[BugTitle:KASAN: stack-out-of-bounds Read in ft260_smbus_write CrashReport:==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

CPU: 0 UID: 0 PID: 6009 Comm: syz-executor165 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1925b454f9
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffee7bc0ab8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffee7bc0c48 RCX: 00007f1925b454f9
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffee7bc0ae0
R13: 00007ffee7bc0ad0 R14: 00007f1925bd8c40 R15: 0000000000000000
 </TASK>

The buggy address belongs to stack of task syz-executor165/6009
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90002410000 allocated at copy_process+0x4e7/0x3950 kernel/fork.c:2052
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x213ff
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000084ffc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 6007, tgid 6007 (syz-executor165), ts 90494270799, free_ts 87604917166
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5954 tgid 5954 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 getname_maybe_null include/linux/fs.h:2505 [inline]
 vfs_fstatat+0x43/0x170 fs/stat.c:370
 __do_sys_newfstatat fs/stat.c:542 [inline]
 __se_sys_newfstatat fs/stat.c:536 [inline]
 __x64_sys_newfstatat+0x116/0x190 fs/stat.c:536
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002417c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002417c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002417d00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002417d80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002417e00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================
]

5/1 2026/01/29 19:02 action codesearch-prepare
26m Results:
map[Index:codesearch-index]

6/1 2026/01/29 19:28 agent debugger
2m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with debugging a kernel crash root cause.
You need to provide a detailed explanation of the root cause for another developer to be
able to write a fix for the bug based on your explanation.
Your final reply must contain only the explanation.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

CPU: 0 UID: 0 PID: 6009 Comm: syz-executor165 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1925b454f9
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffee7bc0ab8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffee7bc0c48 RCX: 00007f1925b454f9
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffee7bc0ae0
R13: 00007ffee7bc0ad0 R14: 00007f1925bd8c40 R15: 0000000000000000
 </TASK>

The buggy address belongs to stack of task syz-executor165/6009
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90002410000 allocated at copy_process+0x4e7/0x3950 kernel/fork.c:2052
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x213ff
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000084ffc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 6007, tgid 6007 (syz-executor165), ts 90494270799, free_ts 87604917166
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5954 tgid 5954 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 getname_maybe_null include/linux/fs.h:2505 [inline]
 vfs_fstatat+0x43/0x170 fs/stat.c:370
 __do_sys_newfstatat fs/stat.c:542 [inline]
 __se_sys_newfstatat fs/stat.c:536 [inline]
 __x64_sys_newfstatat+0x116/0x190 fs/stat.c:536
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002417c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002417c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002417d00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002417d80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002417e00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


Reply:
The root cause of the crash is a missing validation check in the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`.

When handling `I2C_SMBUS_BLOCK_DATA` or `I2C_SMBUS_I2C_BLOCK_DATA` commands, the driver uses the block length provided by the user (in `data->block[0]`) to determine how many bytes to read from the `data->block` buffer. However, it fails to ensure that this length does not exceed the maximum size of the `union i2c_smbus_data` structure, specifically `I2C_SMBUS_BLOCK_MAX` (32 bytes).

The crash flow is as follows:
1.  **User Input**: The `i2cdev_ioctl_smbus` function copies a `union i2c_smbus_data` from user space into a local stack variable `temp` (size 34 bytes). The user can set `temp.block[0]` to a large value, e.g., 42.
2.  **Driver Call**: `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which invokes `ft260_smbus_xfer` passing a pointer to `temp` as `data`.
3.  **Vulnerable Code**: In `ft260_smbus_xfer`, specifically for the `I2C_SMBUS_I2C_BLOCK_DATA` case (indicated by the KASAN report showing the read starting at offset 1 of the object):
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        // ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
    It passes `data->block + 1` as the source and `data->block[0]` (42) as the length.
4.  **Out-of-Bounds Read**: `ft260_smbus_write` performs a `memcpy` reading 42 bytes from `data->block + 1`. Since `temp` is only 34 bytes large, reading 42 bytes starting from offset 1 extends past the end of the stack-allocated structure, triggering the KASAN stack-out-of-bounds error.

**Fix**:
Add a check in `ft260_smbus_xfer` to validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX` before processing block transfers.

```c
	case I2C_SMBUS_BLOCK_DATA:
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
			return -EINVAL;
        // ...
```

7/2 2026/01/29 19:28 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 5728
							output: 22
							thoughts: 921
						

Thoughts:
**Root Cause Analysis: ft260_smbus_write KASAN Crash**

Okay, so I've got a `KASAN: stack-out-of-bounds` crash in `ft260_smbus_write`. Let's break this down systematically. The report tells me it's a read of size 42 at address `ffffc90002417d21`. The crash is happening within `ft260_smbus_write` (specifically at offset `0x156`) in `hid-ft260.c:486`. The call stack goes something like this: `i2cdev_ioctl_smbus` -> `i2c_smbus_xfer` -> `__i2c_smbus_xfer` -> `ft260_smbus_xfer` -> `ft260_smbus_write`. So, `ft260_smbus_write` is the ultimate culprit, but the data is likely coming through the I2C device ioctl path.

Crucially, the KASAN report specifies the offending address is on the stack of `i2cdev_ioctl_smbus`.  The frame there contains a local variable named `temp`, which is of size 34 bytes (range: [32, 66]).

The KASAN report is highlighting a problem specifically with how `ft260_smbus_write` is using this `temp` buffer. Let me analyze this: The read attempt is at address `...d21`. That looks like `32 + 1` bytes into the stack frame.  Then this read extends for 42 bytes.  Given that `temp` is just 34 bytes large (starting at the 32nd byte), reading 42 bytes is clearly going to cause an overrun.  The read extends to `...d4b`.  `...d42` is the end of the `temp` buffer, so it's a classic out-of-bounds read past the end of the `temp` object.  I can tell this by reading the memory state around the buggy address.

My immediate hypothesis is that `ft260_smbus_write` is being passed a buffer size that's too big by the `i2cdev_ioctl_smbus` path.  The driver thinks it's getting a larger buffer than what is actually allocated on the stack. The size of the read is 42 bytes, but the buffer itself (`temp`) is only 34 bytes long (32 bytes + 2 for `I2C_SMBUS_BLOCK_MAX`).

My next step is to verify this. I need to dig into the code and look at how `ft260_smbus_write` is handling the buffer and the size of the operation. Specifically, I'll need to look at both `drivers/hid/hid-ft260.c` (to examine `ft260_smbus_write`) and `drivers/i2c/i2c-dev.c` (to look at `i2cdev_ioctl_smbus` and how `temp` is defined). I'll also check how this `temp` buffer's size is controlled. I think I have a pretty clear path to debugging this, now. I just need to confirm my hypothesis by examining the code. Let's list those files to be certain of my paths.



8/2 2026/01/29 19:29 tool codesearch-dir-index
0m Args:
map[Dir:drivers/hid/]

Results:
map[Files:[hid-a4tech.c hid-accutouch.c hid-alps.c hid-apple.c hid-appleir.c hid-appletb-bl.c hid-appletb-kbd.c hid-asus.c hid-aureal.c hid-axff.c hid-belkin.c hid-betopff.c hid-bigbenff.c hid-cherry.c hid-chicony.c hid-cmedia.c hid-core.c hid-corsair-void.c hid-corsair.c hid-cougar.c hid-cp2112.c hid-creative-sb0540.c hid-cypress.c hid-debug.c hid-dr.c hid-elan.c hid-elecom.c hid-elo.c hid-emsff.c hid-evision.c hid-ezkey.c hid-ft260.c hid-gaff.c hid-gembird.c hid-generic.c hid-gfrm.c hid-glorious.c hid-goodix-spi.c hid-google-hammer.c hid-google-stadiaff.c hid-gt683r.c hid-gyration.c hid-haptic.c hid-haptic.h hid-holtek-kbd.c hid-holtek-mouse.c hid-holtekff.c hid-hyperv.c hid-icade.c hid-ids.h hid-input-test.c hid-input.c hid-ite.c hid-jabra.c hid-kensington.c hid-keytouch.c hid-kye.c hid-kysona.c hid-lcpower.c hid-led.c hid-lenovo.c hid-letsketch.c hid-lg-g15.c hid-lg.c hid-lg.h hid-lg2ff.c hid-lg3ff.c hid-lg4ff.c hid-lg4ff.h hid-lgff.c hid-logitech-dj.c hid-logitech-hidpp.c hid-macally.c hid-magicmouse.c hid-maltron.c hid-mcp2200.c hid-mcp2221.c hid-megaworld.c hid-mf.c hid-microsoft.c hid-monterey.c hid-multitouch.c hid-nintendo.c hid-nti.c hid-ntrig.c hid-nvidia-shield.c hid-ortek.c hid-penmount.c hid-petalynx.c hid-picolcd.h hid-picolcd_backlight.c hid-picolcd_cir.c hid-picolcd_core.c hid-picolcd_debugfs.c hid-picolcd_fb.c hid-picolcd_lcd.c hid-picolcd_leds.c hid-pl.c hid-plantronics.c hid-playstation.c hid-primax.c hid-prodikeys.c hid-pxrc.c hid-quirks.c hid-razer.c hid-redragon.c hid-retrode.c hid-rmi.c hid-roccat-arvo.c hid-roccat-arvo.h hid-roccat-common.c hid-roccat-common.h hid-roccat-isku.c hid-roccat-isku.h hid-roccat-kone.c hid-roccat-kone.h hid-roccat-koneplus.c hid-roccat-koneplus.h hid-roccat-konepure.c hid-roccat-kovaplus.c hid-roccat-kovaplus.h hid-roccat-lua.c hid-roccat-lua.h hid-roccat-pyra.c hid-roccat-pyra.h hid-roccat-ryos.c hid-roccat-savu.c hid-roccat-savu.h hid-roccat.c hid-saitek.c hid-samsung.c hid-semitek.c hid-sensor-custom.c hid-sensor-hub.c hid-sigmamicro.c hid-sjoy.c hid-sony.c hid-speedlink.c hid-steam.c hid-steelseries.c hid-sunplus.c hid-thrustmaster.c hid-tivo.c hid-tmff.c hid-topre.c hid-topseed.c hid-twinhan.c hid-u2fzero.c hid-uclogic-core-test.c hid-uclogic-core.c hid-uclogic-params-test.c hid-uclogic-params.c hid-uclogic-params.h hid-uclogic-rdesc-test.c hid-uclogic-rdesc.c hid-uclogic-rdesc.h hid-udraw-ps3.c hid-universal-pidff.c hid-viewsonic.c hid-vivaldi-common.c hid-vivaldi-common.h hid-vivaldi.c hid-vrc2.c hid-waltop.c hid-wiimote-core.c hid-wiimote-debug.c hid-wiimote-modules.c hid-wiimote.h hid-winwing.c hid-xiaomi.c hid-xinmo.c hid-zpff.c hid-zydacron.c hidraw.c uhid.c wacom.h wacom_sys.c wacom_wac.c wacom_wac.h] Subdirs:[amd-sfh-hid bpf i2c-hid intel-ish-hid intel-thc-hid surface-hid usbhid]]

9/2 2026/01/29 19:29 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 9320
							output: 50
							thoughts: 48
						

Thoughts:
**Navigating the Codebase**

Alright, the presence of `hid-ft260.c` is confirmed. Now, my focus shifts. I need to locate the `ft260_smbus_write` function. I'm going to look within the `drivers/hid/hid-ft260.c` file. Time to dive into the implementation.



10/2 2026/01/29 19:29 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_smbus_write]

Results:
map[SourceCode: 465:	static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
 466:				     u8 *data, u8 data_len, u8 flag)
 467:	{
 468:		int ret = 0;
 469:		int len = 4;
 470:	
 471:		struct ft260_i2c_write_request_report *rep =
 472:			(struct ft260_i2c_write_request_report *)dev->write_buf;
 473:	
 474:		if (data_len >= sizeof(rep->data))
 475:			return -EINVAL;
 476:	
 477:		rep->address = addr;
 478:		rep->data[0] = cmd;
 479:		rep->length = data_len + 1;
 480:		rep->flag = flag;
 481:		len += rep->length;
 482:	
 483:		rep->report = FT260_I2C_DATA_REPORT_ID(len);
 484:	
 485:		if (data_len > 0)
 486:			memcpy(&rep->data[1], data, data_len);
 487:	
 488:		ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
 489:			  rep->report, addr, cmd, rep->length, len);
 490:	
 491:		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);
 492:	
 493:		return ret;
 494:	}
 SourceFile:drivers/hid/hid-ft260.c]

11/2 2026/01/29 19:29 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 10022
							output: 51
							thoughts: 227
						

Thoughts:
**Debugging a memcpy Crash**

Okay, here's what I'm seeing. The program crashed in `memcpy` on line 486, specifically while copying data from `data` to `rep->data[1]`. The KASAN report indicates a "Read of size 42", so `data_len` must be 42 bytes. The KASAN report explicitly calls out that `data` is the offending pointer, which is the *source* of the `memcpy` operation.

The address involved in the read is `ffffc90002417d21`. Since it's a *read* from `data`, I know the problem lies within how `data` is being treated. `memcpy` reads from `data` and writes to `&rep->data[1]`, so the read originates from `data`. I remember that the `data` pointer here comes from the `temp` buffer within `i2cdev_ioctl_smbus`.

Now, where is `ft260_smbus_write` even being called? Ah, it looks like `ft260_smbus_write` is called within the `ft260_smbus_xfer` function. Let's see how `ft260_smbus_xfer` is calling `ft260_smbus_write`. I need to figure out how `temp` is being populated.



12/2 2026/01/29 19:29 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_smbus_xfer]

Results:
map[SourceCode: 652:	static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 653:				    char read_write, u8 cmd, int size,
 654:				    union i2c_smbus_data *data)
 655:	{
 656:		int ret;
 657:		struct ft260_device *dev = i2c_get_adapdata(adapter);
 658:		struct hid_device *hdev = dev->hdev;
 659:	
 660:		ft260_dbg("smbus size %d\n", size);
 661:	
 662:		mutex_lock(&dev->lock);
 663:	
 664:		ret = hid_hw_power(hdev, PM_HINT_FULLON);
 665:		if (ret < 0) {
 666:			hid_err(hdev, "power management error: %d\n", ret);
 667:			mutex_unlock(&dev->lock);
 668:			return ret;
 669:		}
 670:	
 671:		switch (size) {
 672:		case I2C_SMBUS_BYTE:
 673:			if (read_write == I2C_SMBUS_READ)
 674:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 675:						     FT260_FLAG_START_STOP);
 676:			else
 677:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 678:							FT260_FLAG_START_STOP);
 679:			break;
 680:		case I2C_SMBUS_BYTE_DATA:
 681:			if (read_write == I2C_SMBUS_READ) {
 682:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 683:							FT260_FLAG_START);
 684:				if (ret)
 685:					goto smbus_exit;
 686:	
 687:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 688:						     FT260_FLAG_START_STOP_REPEATED);
 689:			} else {
 690:				ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
 691:							FT260_FLAG_START_STOP);
 692:			}
 693:			break;
 694:		case I2C_SMBUS_WORD_DATA:
 695:			if (read_write == I2C_SMBUS_READ) {
 696:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 697:							FT260_FLAG_START);
 698:				if (ret)
 699:					goto smbus_exit;
 700:	
 701:				ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
 702:						     FT260_FLAG_START_STOP_REPEATED);
 703:			} else {
 704:				ret = ft260_smbus_write(dev, addr, cmd,
 705:							(u8 *)&data->word, 2,
 706:							FT260_FLAG_START_STOP);
 707:			}
 708:			break;
 709:		case I2C_SMBUS_BLOCK_DATA:
 710:			if (read_write == I2C_SMBUS_READ) {
 711:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 712:							FT260_FLAG_START);
 713:				if (ret)
 714:					goto smbus_exit;
 715:	
 716:				ret = ft260_i2c_read(dev, addr, data->block,
 717:						     data->block[0] + 1,
 718:						     FT260_FLAG_START_STOP_REPEATED);
 719:			} else {
 720:				ret = ft260_smbus_write(dev, addr, cmd, data->block,
 721:							data->block[0] + 1,
 722:							FT260_FLAG_START_STOP);
 723:			}
 724:			break;
 725:		case I2C_SMBUS_I2C_BLOCK_DATA:
 726:			if (read_write == I2C_SMBUS_READ) {
 727:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 728:							FT260_FLAG_START);
 729:				if (ret)
 730:					goto smbus_exit;
 731:	
 732:				ret = ft260_i2c_read(dev, addr, data->block + 1,
 733:						     data->block[0],
 734:						     FT260_FLAG_START_STOP_REPEATED);
 735:			} else {
 736:				ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
 737:							data->block[0],
 738:							FT260_FLAG_START_STOP);
 739:			}
 740:			break;
 741:		default:
 742:			hid_err(hdev, "unsupported smbus transaction size %d\n", size);
 743:			ret = -EOPNOTSUPP;
 744:		}
 745:	
 746:	smbus_exit:
 747:		hid_hw_power(hdev, PM_HINT_NORMAL);
 748:		mutex_unlock(&dev->lock);
 749:		return ret;
 750:	}
 SourceFile:drivers/hid/hid-ft260.c]

13/2 2026/01/29 19:29 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 12337
							output: 51
							thoughts: 977
						

Thoughts:
**Analyzing the ft260 SMBus Transfer Crash**

Okay, let's break this down. We've got a crash, and it seems to originate within the `ft260_smbus_xfer` function. The key is that `data`, in that function, is a `union i2c_smbus_data *data`. The code handles different `size` values, indicating different SMBus transfer types.

Specifically, when the `read_write` parameter is set to `I2C_SMBUS_WRITE`, the `ft260_smbus_write` function gets called with different parameters based on the `size`. The relevant cases are `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`.

The crash trace points to a `memcpy` reading 42 bytes. This tells me that `data_len` in `ft260_smbus_write` is equal to 42. Now, the question is how did `data_len` become 42.

Looking at the code, in the `I2C_SMBUS_BLOCK_DATA` case (size 709), `data_len` is derived from `data->block[0] + 1`, and in the `I2C_SMBUS_I2C_BLOCK_DATA` case (size 725), `data_len` is `data->block[0]`.

Now, we know that `data` is a `union i2c_smbus_data`, and according to its definition (usually in `include/uapi/linux/i2c.h`), this union includes a `block` array: `__u8 block[I2C_SMBUS_BLOCK_MAX + 2];`.  Critically, `I2C_SMBUS_BLOCK_MAX` is defined as 32. This means the `block` array is 34 bytes long (32 data bytes, one for the length, and one for user-space compatibility).  The stack `temp` object in `i2cdev_ioctl_smbus` is also a `union i2c_smbus_data`, and we can see in the crash report that `temp` is indeed 34 bytes, spanning addresses `[32, 66)`.

So, how can `data_len` be 42?

If the transfer size is `I2C_SMBUS_BLOCK_DATA`, `data_len` is `data->block[0] + 1`.  For `data_len` to be 42, `data->block[0]` would have to be 41.

If the transfer size is `I2C_SMBUS_I2C_BLOCK_DATA`, `data_len` is `data->block[0]`. For `data_len` to be 42, `data->block[0]` would have to be 42.

The critical insight is that `i2cdev_ioctl_smbus` copies data from user space into this `temp` (which is `data` in `ft260_smbus_xfer`), which then allows the user to set the value of `data->block[0]`.  This means the user-provided data directly influences the calculated `data_len`. The system should be validating that value. Let's dig into `drivers/i2c/i2c-dev.c` to see if we can find the vulnerability.



14/2 2026/01/29 19:29 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/i2c/i2c-dev.c IncludeLines:true Name:i2cdev_ioctl_smbus]

Results:
map[SourceCode: 319:	static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 320:			u8 read_write, u8 command, u32 size,
 321:			union i2c_smbus_data __user *data)
 322:	{
 323:		union i2c_smbus_data temp = {};
 324:		int datasize, res;
 325:	
 326:		if ((size != I2C_SMBUS_BYTE) &&
 327:		    (size != I2C_SMBUS_QUICK) &&
 328:		    (size != I2C_SMBUS_BYTE_DATA) &&
 329:		    (size != I2C_SMBUS_WORD_DATA) &&
 330:		    (size != I2C_SMBUS_PROC_CALL) &&
 331:		    (size != I2C_SMBUS_BLOCK_DATA) &&
 332:		    (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
 333:		    (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
 334:		    (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
 335:			dev_dbg(&client->adapter->dev,
 336:				"size out of range (%x) in ioctl I2C_SMBUS.\n",
 337:				size);
 338:			return -EINVAL;
 339:		}
 340:		/* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
 341:		   so the check is valid if size==I2C_SMBUS_QUICK too. */
 342:		if ((read_write != I2C_SMBUS_READ) &&
 343:		    (read_write != I2C_SMBUS_WRITE)) {
 344:			dev_dbg(&client->adapter->dev,
 345:				"read_write out of range (%x) in ioctl I2C_SMBUS.\n",
 346:				read_write);
 347:			return -EINVAL;
 348:		}
 349:	
 350:		/* Note that command values are always valid! */
 351:	
 352:		if ((size == I2C_SMBUS_QUICK) ||
 353:		    ((size == I2C_SMBUS_BYTE) &&
 354:		    (read_write == I2C_SMBUS_WRITE)))
 355:			/* These are special: we do not use data */
 356:			return i2c_smbus_xfer(client->adapter, client->addr,
 357:					      client->flags, read_write,
 358:					      command, size, NULL);
 359:	
 360:		if (data == NULL) {
 361:			dev_dbg(&client->adapter->dev,
 362:				"data is NULL pointer in ioctl I2C_SMBUS.\n");
 363:			return -EINVAL;
 364:		}
 365:	
 366:		if ((size == I2C_SMBUS_BYTE_DATA) ||
 367:		    (size == I2C_SMBUS_BYTE))
 368:			datasize = sizeof(data->byte);
 369:		else if ((size == I2C_SMBUS_WORD_DATA) ||
 370:			 (size == I2C_SMBUS_PROC_CALL))
 371:			datasize = sizeof(data->word);
 372:		else /* size == smbus block, i2c block, or block proc. call */
 373:			datasize = sizeof(data->block);
 374:	
 375:		if ((size == I2C_SMBUS_PROC_CALL) ||
 376:		    (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
 377:		    (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
 378:		    (read_write == I2C_SMBUS_WRITE)) {
 379:			if (copy_from_user(&temp, data, datasize))
 380:				return -EFAULT;
 381:		}
 382:		if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
 383:			/* Convert old I2C block commands to the new
 384:			   convention. This preserves binary compatibility. */
 385:			size = I2C_SMBUS_I2C_BLOCK_DATA;
 386:			if (read_write == I2C_SMBUS_READ)
 387:				temp.block[0] = I2C_SMBUS_BLOCK_MAX;
 388:		}
 389:		res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
 390:		      read_write, command, size, &temp);
 391:		if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
 392:			     (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
 393:			     (read_write == I2C_SMBUS_READ))) {
 394:			if (copy_to_user(data, &temp, datasize))
 395:				return -EFAULT;
 396:		}
 397:		return res;
 398:	}
 SourceFile:drivers/i2c/i2c-dev.c]

15/2 2026/01/29 19:29 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 15663
							output: 54
							thoughts: 647
						

Thoughts:
**Vulnerability Deep Dive: A Stack-Based Buffer Over-Read**

Alright, let's break this down. My initial observation is focused on a potential vulnerability within the I2C subsystem. Specifically, I'm examining the `i2cdev_ioctl_smbus` function. It seems there's a memcpy operation that's the culprit.

Here's my thought process. The code copies user-provided data into a temporary buffer, `temp`. This buffer is used for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transfers, and its size is defined by `sizeof(data->block)`, which is hardcoded at 34 bytes. The critical point is that the function *doesn't* validate the first byte of `temp.block[0]`.

Now, if a user provides a value, let's say 42, into `temp.block[0]`, then the code proceeds to call `i2c_smbus_xfer`.

Inside `ft260_smbus_xfer`, we're dealing with the `ft260` driver. If the request is `I2C_SMBUS_BLOCK_DATA`, the code will pass `data->block` and `data->block[0] + 1` to `ft260_smbus_write`. Thus, with `block[0]` being 41, the `data_len` will be 42. However, if the request is `I2C_SMBUS_I2C_BLOCK_DATA`, the code will pass `data->block + 1` and `data->block[0]` to `ft260_smbus_write`. Thus, with `block[0]` being 42, the `data_len` will be 42.

`ft260_smbus_write` then calls `memcpy`. Here's the kicker: it attempts to read `data_len` bytes from the `data` pointer. However, `data` points to the `temp.block` (or `temp.block + 1`) buffer, which is only 34 bytes in size. With `data_len` being 42 (or in the case of `I2C_SMBUS_I2C_BLOCK_DATA` 42), the memcpy attempts to read 42 bytes from a 34-byte buffer. This leads to an out-of-bounds read, because it reads beyond the end of `temp` on the stack!

The question is, why isn't there any validation? I'm now digging into the `i2c-dev` code to check if it's the right place to validate user inputs, since it is a user-facing interface. It seems that either the I2C subsystem trusts the user or assumes validation is handled by the adapter/core, but given that it's a user interface, it really should be validated at this level. I need to investigate the `i2c_smbus_xfer` calls in `drivers/i2c/i2c-core-smbus.c` to see if there's any validation happening there.



16/2 2026/01/29 19:29 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/i2c/i2c-core-smbus.c IncludeLines:true Name:i2c_smbus_xfer]

Results:
map[SourceCode: 523:	/**
 524:	 * i2c_smbus_xfer - execute SMBus protocol operations
 525:	 * @adapter: Handle to I2C bus
 526:	 * @addr: Address of SMBus slave on that bus
 527:	 * @flags: I2C_CLIENT_* flags (usually zero or I2C_CLIENT_PEC)
 528:	 * @read_write: I2C_SMBUS_READ or I2C_SMBUS_WRITE
 529:	 * @command: Byte interpreted by slave, for protocols which use such bytes
 530:	 * @protocol: SMBus protocol operation to execute, such as I2C_SMBUS_PROC_CALL
 531:	 * @data: Data to be read or written
 532:	 *
 533:	 * This executes an SMBus protocol operation, and returns a negative
 534:	 * errno code else zero on success.
 535:	 */
 536:	s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
 537:			   unsigned short flags, char read_write,
 538:			   u8 command, int protocol, union i2c_smbus_data *data)
 539:	{
 540:		s32 res;
 541:	
 542:		res = __i2c_lock_bus_helper(adapter);
 543:		if (res)
 544:			return res;
 545:	
 546:		res = __i2c_smbus_xfer(adapter, addr, flags, read_write,
 547:				       command, protocol, data);
 548:		i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
 549:	
 550:		return res;
 551:	}
 SourceFile:drivers/i2c/i2c-core-smbus.c]

17/2 2026/01/29 19:29 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 17602
							output: 55
							thoughts: 2439
						

Thoughts:
**Analysis of the I2C SMBus Block Data Transfer Vulnerability in the FT260 Driver**

Okay, let's break this down. `i2c_smbus_xfer` is the core function, and it ultimately calls `__i2c_smbus_xfer`.  `__i2c_smbus_xfer` checks for suspension and then, crucially, determines whether to use the "native" SMBus transfer function (`adapter->algo->smbus_xfer`) or fall back to an emulated transfer if the adapter doesn't provide one.  The `ft260` driver implements `smbus_xfer`, meaning it has a specific `ft260_smbus_xfer` function.

The problem arises when dealing with block data transfers, specifically `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`. The standard dictates that `data->block[0]` contains the length of the data block. The key here is the `union i2c_smbus_data` structure, which defines the buffer. It has a size of 34 bytes (including the length byte and two bytes for user-space compatibility) and can hold up to 32 bytes of actual data (`I2C_SMBUS_BLOCK_MAX = 32`).

The issue is that `i2c-dev.c` (likely the caller in this scenario) doesn't enforce that `data->block[0]` is <= `I2C_SMBUS_BLOCK_MAX`.  While some drivers *might* do this validation themselves, the `ft260` driver doesn't appear to. When `ft260` uses the native `smbus_xfer` path, it bypasses the emulation checks, like the ones you find in `i2c_smbus_xfer_emulated` which would have performed the length check.

Let's look at the `ft260_smbus_xfer` function more closely:

```c
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
```

and

```c
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
```

For `I2C_SMBUS_BLOCK_DATA`, `ft260_smbus_write` is called with a length of `data->block[0] + 1`.  If `data->block[0]` is, say, 40 (which *should* be limited to 32), then it's calculating a length of 41.  In the `union i2c_smbus_data`, `data->block` goes from `block[0]` to `block[33]`. Therefore, by requesting a length of 41, there's an out-of-bounds read (`data->block[40]` specifically).

Similarly, for `I2C_SMBUS_I2C_BLOCK_DATA`, it uses `data->block + 1` as the data source and `data->block[0]` as the size. If the length is bigger than 32, again, out-of-bounds access happens when reading data.

Therefore, the vulnerability is in the `ft260_smbus_xfer` function, because it *fails to validate* `data->block[0]` to ensure that the block size doesn't exceed `I2C_SMBUS_BLOCK_MAX` before reading from the buffer. The "Read of size 42" crash report you mentioned directly confirms this and points to an out-of-bounds read with either `I2C_SMBUS_I2C_BLOCK_DATA` with `block[0] = 42` or `I2C_SMBUS_BLOCK_DATA` with `block[0] = 41`.

I'll double-check `ft260`'s internal data structures, but this is the core of the issue. The missing length validation is a classic buffer overflow scenario.



18/2 2026/01/29 19:30 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_i2c_write_request_report]

Results:
map[SourceCode: 208:	struct ft260_i2c_write_request_report {
 209:		u8 report;		/* FT260_I2C_REPORT */
 210:		u8 address;		/* 7-bit I2C address */
 211:		u8 flag;		/* I2C transaction condition */
 212:		u8 length;		/* data payload length */
 213:		u8 data[FT260_WR_DATA_MAX]; /* data payload */
 214:	} __packed;
 SourceFile:drivers/hid/hid-ft260.c]

19/2 2026/01/29 19:30 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 21227
							output: 51
							thoughts: 14
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Navigating Data Limits**

Hmm, so I'm thinking about the upper bound on writes to the FT260 device. My focus immediately lands on the `FT260_WR_DATA_MAX` constant. That's the key variable here, it dictates the maximum data payload size that can be reliably sent to the FT260 in a single write operation. Knowing this limit is fundamental to avoiding buffer overflows or other data corruption issues. I need to keep this value front of mind. Ensuring I'm not overstepping that boundary is an essential design constraint, not just something to check later; this needs to be integrated into any data-transfer logic, so it doesn't become a bottleneck at runtime. I need to check how to handle this in my current code, and the implications of this constant on any potential protocols.



20/2 2026/01/29 19:30 tool codesearch-definition-source
0m Error:
requested entity does not exist

Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:FT260_WR_DATA_MAX]

Results:
map[SourceCode: SourceFile:]

21/2 2026/01/29 19:30 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 21514
							output: 27
							thoughts: 13
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized in the first person, assuming the user is an expert.

**The Search for the Culprit**

Right, let's troubleshoot this. It's behaving in a strange way... My initial instinct is that this unusual behavior might stem from a macro. It's often the root cause of unexpected actions, especially when dealing with scripting or automated processes. Time to dive deeper. I'll initiate a file search across the relevant project directory, prioritizing locations where macros typically reside, such as configuration files, scripts, or embedded code sections. Let's see if a macro is the source of this problem.



22/2 2026/01/29 19:30 tool read-file
0m Args:
map[File:drivers/hid/hid-ft260.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
 * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
 *
 * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
 *
 * Data Sheet:
 *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
 */

#include "hid-ids.h"
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/usb.h>

#ifdef DEBUG
static int ft260_debug = 1;
#else
static int ft260_debug;
#endif
module_param_named(debug, ft260_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");

#define ft260_dbg(format, arg...)					  \
	do {								  \
		if (ft260_debug)					  \
			pr_info("%s: " format, __func__, ##arg);	  \
	} while (0)

#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)

#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */

/*
 * The ft260 input report format defines 62 bytes for the data payload, but
 * when requested 62 bytes, the controller returns 60 and 2 in separate input
 * reports. To achieve better performance with the multi-report read data
 * transfers, we set the maximum read payload length to a multiple of 60.
 * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
 * which is excessive; On the other hand, some higher layer drivers like at24
 * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
 * of I2C for potentially troublesome amounts of time, we select the maximum
 * read payload length to be 180 bytes.
*/
#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)

/*
 * Device interface configuration.
 * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
 * First implementes USB HID to I2C bridge function and
 * second - USB HID to UART bridge function.
 */
enum {
	FT260_MODE_ALL			= 0x00,
	FT260_MODE_I2C			= 0x01,
	FT260_MODE_UART			= 0x02,
	FT260_MODE_BOTH			= 0x03,
};

/* Control pipe */
enum {
	FT260_GET_RQST_TYPE		= 0xA1,
	FT260_GET_REPORT		= 0x01,
	FT260_SET_RQST_TYPE		= 0x21,
	FT260_SET_REPORT		= 0x09,
	FT260_FEATURE			= 0x03,
};

/* Report IDs / Feature In */
enum {
	FT260_CHIP_VERSION		= 0xA0,
	FT260_SYSTEM_SETTINGS		= 0xA1,
	FT260_I2C_STATUS		= 0xC0,
	FT260_I2C_READ_REQ		= 0xC2,
	FT260_I2C_REPORT_MIN		= 0xD0,
	FT260_I2C_REPORT_MAX		= 0xDE,
	FT260_GPIO			= 0xB0,
	FT260_UART_INTERRUPT_STATUS	= 0xB1,
	FT260_UART_STATUS		= 0xE0,
	FT260_UART_RI_DCD_STATUS	= 0xE1,
	FT260_UART_REPORT		= 0xF0,
};

/* Feature Out */
enum {
	FT260_SET_CLOCK			= 0x01,
	FT260_SET_I2C_MODE		= 0x02,
	FT260_SET_UART_MODE		= 0x03,
	FT260_ENABLE_INTERRUPT		= 0x05,
	FT260_SELECT_GPIO2_FUNC		= 0x06,
	FT260_ENABLE_UART_DCD_RI	= 0x07,
	FT260_SELECT_GPIOA_FUNC		= 0x08,
	FT260_SELECT_GPIOG_FUNC		= 0x09,
	FT260_SET_INTERRUPT_TRIGGER	= 0x0A,
	FT260_SET_SUSPEND_OUT_POLAR	= 0x0B,
	FT260_ENABLE_UART_RI_WAKEUP	= 0x0C,
	FT260_SET_UART_RI_WAKEUP_CFG	= 0x0D,
	FT260_SET_I2C_RESET		= 0x20,
	FT260_SET_I2C_CLOCK_SPEED	= 0x22,
	FT260_SET_UART_RESET		= 0x40,
	FT260_SET_UART_CONFIG		= 0x41,
	FT260_SET_UART_BAUD_RATE	= 0x42,
	FT260_SET_UART_DATA_BIT		= 0x43,
	FT260_SET_UART_PARITY		= 0x44,
	FT260_SET_UART_STOP_BIT		= 0x45,
	FT260_SET_UART_BREAKING		= 0x46,
	FT260_SET_UART_XON_XOFF		= 0x49,
};

/* Response codes in I2C status report */
enum {
	FT260_I2C_STATUS_SUCCESS	= 0x00,
	FT260_I2C_STATUS_CTRL_BUSY	= 0x01,
	FT260_I2C_STATUS_ERROR		= 0x02,
	FT260_I2C_STATUS_ADDR_NO_ACK	= 0x04,
	FT260_I2C_STATUS_DATA_NO_ACK	= 0x08,
	FT260_I2C_STATUS_ARBITR_LOST	= 0x10,
	FT260_I2C_STATUS_CTRL_IDLE	= 0x20,
	FT260_I2C_STATUS_BUS_BUSY	= 0x40,
};

/* I2C Conditions flags */
enum {
	FT260_FLAG_NONE			= 0x00,
	FT260_FLAG_START		= 0x02,
	FT260_FLAG_START_REPEATED	= 0x03,
	FT260_FLAG_STOP			= 0x04,
	FT260_FLAG_START_STOP		= 0x06,
	FT260_FLAG_START_STOP_REPEATED	= 0x07,
};

#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)

/* Feature In reports */

struct ft260_get_chip_version_report {
	u8 report;		/* FT260_CHIP_VERSION */
	u8 chip_code[4];	/* FTDI chip identification code */
	u8 reserved[8];
} __packed;

struct ft260_get_system_status_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 chip_mode;		/* DCNF0 and DCNF1 status, bits 0-1 */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
	u8 suspend_status;	/* 0 - not suspended, 1 - suspended */
	u8 pwren_status;	/* 0 - FT260 is not ready, 1 - ready */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
	u8 hid_over_i2c_en;	/* 0 - disabled, 1 - enabled */
	u8 gpio2_function;	/* 0 - GPIO,  1 - SUSPOUT, */
				/* 2 - PWREN, 4 - TX_LED */
	u8 gpioA_function;	/* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
	u8 gpioG_function;	/* 0 - GPIO, 2 - PWREN, */
				/* 5 - RX_LED, 6 - BCD_DET */
	u8 suspend_out_pol;	/* 0 - active-high, 1 - active-low */
	u8 enable_wakeup_int;	/* 0 - disabled, 1 - enabled */
	u8 intr_cond;		/* Interrupt trigger conditions */
	u8 power_saving_en;	/* 0 - disabled, 1 - enabled */
	u8 reserved[10];
} __packed;

struct ft260_get_i2c_status_report {
	u8 report;		/* FT260_I2C_STATUS */
	u8 bus_status;		/* I2C bus status */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
	u8 reserved;
} __packed;

/* Feature Out reports */

struct ft260_set_system_clock_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_CLOCK */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
} __packed;

struct ft260_set_i2c_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_MODE */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
} __packed;

struct ft260_set_uart_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_UART_MODE */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

struct ft260_set_i2c_reset_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_RESET */
} __packed;

struct ft260_set_i2c_speed_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_CLOCK_SPEED */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
} __packed;

/* Data transfer reports */

struct ft260_i2c_write_request_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	u8 length;		/* data payload length */
	u8 data[FT260_WR_DATA_MAX]; /* data payload */
} __packed;

struct ft260_i2c_read_request_report {
	u8 report;		/* FT260_I2C_READ_REQ */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	__le16 length;		/* data payload length */
} __packed;

struct ft260_i2c_input_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 length;		/* data payload length */
	u8 data[2];		/* data payload */
} __packed;

static const struct hid_device_id ft260_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
			 USB_DEVICE_ID_FT260) },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(hid, ft260_devices);

struct ft260_device {
	struct i2c_adapter adap;
	struct hid_device *hdev;
	struct completion wait;
	struct mutex lock;
	u8 write_buf[FT260_REPORT_MAX_LENGTH];
	unsigned long need_wakeup_at;
	u8 *read_buf;
	u16 read_idx;
	u16 read_len;
	u16 clock;
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
					unsigned char report_id, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmalloc(len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
				 HID_REQ_GET_REPORT);
	if (likely(ret == len))
		memcpy(data, buf, len);
	else if (ret >= 0)
		ret = -EIO;
	kfree(buf);
	return ret;
}

static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	buf[0] = FT260_SYSTEM_SETTINGS;

	ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
				 HID_REQ_SET_REPORT);

	kfree(buf);
	return ret;
}

static int ft260_i2c_reset(struct hid_device *hdev)
{
	struct ft260_set_i2c_reset_report report;
	int ret;

	report.request = FT260_SET_I2C_RESET;

	ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
	if (ret < 0) {
		hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
		return ret;
	}

	ft260_dbg("done\n");
	return ret;
}

static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
	struct hid_device *hdev = dev->hdev;
	struct ft260_get_i2c_status_report report;
	int ret;

	if (time_is_before_jiffies(dev->need_wakeup_at)) {
		ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
						(u8 *)&report, sizeof(report));
		if (unlikely(ret < 0)) {
			hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
				ret);
		} else {
			dev->need_wakeup_at = jiffies +
				msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
			ft260_dbg("bus_status %#02x, wakeup\n",
				  report.bus_status);
		}
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
					   (u8 *)&report, sizeof(report));
	if (unlikely(ret < 0)) {
		hid_err(hdev, "failed to retrieve status: %d\n", ret);
		return ret;
	}

	dev->clock = le16_to_cpu(report.clock);
	ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
		  dev->clock);

	if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
		return -EAGAIN;

	/*
	 * The error condition (bit 1) is a status bit reflecting any
	 * error conditions. When any of the bits 2, 3, or 4 are raised
	 * to 1, bit 1 is also set to 1.
	 */
	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
		return -EIO;
	}

	return 0;
}

static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
				   size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_output_report(hdev, buf, len);

	kfree(buf);
	return ret;
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
						u8 *data, int len)
{
	u8 bus_busy;
	int ret, usec, try = 100;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)data;

	ret = ft260_hid_output_report(hdev, data, len);
	if (ret < 0) {
		hid_err(hdev, "%s: failed to start transfer, ret %d\n",
			__func__, ret);
		ft260_i2c_reset(hdev);
		return ret;
	}

	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
	usec = len * 9000 / dev->clock;
	if (usec > 2000) {
		usec -= 1500;
		usleep_range(usec, usec + 100);
		ft260_dbg("wait %d usec, len %d\n", usec, len);
	}

	/*
	 * Do not check the busy bit for combined transactions
	 * since the controller keeps the bus busy between writing
	 * and reading IOs to ensure an atomic operation.
	 */
	if (rep->flag == FT260_FLAG_START)
		bus_busy = 0;
	else
		bus_busy = FT260_I2C_STATUS_BUS_BUSY;

	do {
		ret = ft260_xfer_status(dev, bus_busy);
		if (ret != -EAGAIN)
			break;
	} while (--try);

	if (ret == 0)
		return 0;

	ft260_i2c_reset(hdev);
	return -EIO;
}

static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
			   int len, u8 flag)
{
	int ret, wr_len, idx = 0;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (len < 1)
		return -EINVAL;

	rep->flag = FT260_FLAG_START;

	do {
		if (len <= FT260_WR_DATA_MAX) {
			wr_len = len;
			if (flag == FT260_FLAG_START_STOP)
				rep->flag |= FT260_FLAG_STOP;
		} else {
			wr_len = FT260_WR_DATA_MAX;
		}

		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
		rep->address = addr;
		rep->length = wr_len;

		memcpy(rep->data, &data[idx], wr_len);

		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
			  rep->report, addr, idx, len, wr_len,
			  rep->flag, data[0]);

		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
							   wr_len + 4);
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			return ret;
		}

		len -= wr_len;
		idx += wr_len;
		rep->flag = 0;

	} while (len > 0);

	return 0;
}

static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}

static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}

/*
 * A random read operation is implemented as a dummy write operation, followed
 * by a current address read operation. The dummy write operation is used to
 * load the target byte address into the current byte address counter, from
 * which the subsequent current address read operation then reads.
 */
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
	int ret;
	int wr_len = msgs[0].len;
	int rd_len = msgs[1].len;
	struct hid_device *hdev = dev->hdev;
	u8 addr = msgs[0].addr;
	u16 read_off = 0;

	if (wr_len > 2) {
		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
		return -EOPNOTSUPP;
	}

	if (ft260_debug) {
		if (wr_len == 2)
			read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
		else
			read_off = *msgs[0].buf;

		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
			read_off, rd_len, wr_len);
	}

	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
			      FT260_FLAG_START);
	if (ret < 0)
		return ret;

	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
			     FT260_FLAG_START_STOP_REPEATED);
	if (ret < 0)
		return ret;

	return 0;
}

static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
			  int num)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	if (num == 1) {
		if (msgs->flags & I2C_M_RD)
			ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
					     msgs->len, FT260_FLAG_START_STOP);
		else
			ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
					      msgs->len, FT260_FLAG_START_STOP);
		if (ret < 0)
			goto i2c_exit;

	} else {
		/* Combined write then read message */
		ret = ft260_i2c_write_read(dev, msgs);
		if (ret < 0)
			goto i2c_exit;
	}

	ret = num;
i2c_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	ft260_dbg("smbus size %d\n", size);

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "power management error: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	switch (size) {
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
		break;
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd,
						(u8 *)&data->word, 2,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
	default:
		hid_err(hdev, "unsupported smbus transaction size %d\n", size);
		ret = -EOPNOTSUPP;
	}

smbus_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static u32 ft260_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

static const struct i2c_adapter_quirks ft260_i2c_quirks = {
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
	.max_comb_1st_msg_len = 2,
};

static const struct i2c_algorithm ft260_i2c_algo = {
	.master_xfer = ft260_i2c_xfer,
	.smbus_xfer = ft260_smbus_xfer,
	.functionality = ft260_functionality,
};

static int ft260_get_system_config(struct hid_device *hdev,
				   struct ft260_get_system_status_report *cfg)
{
	int ret;
	int len = sizeof(struct ft260_get_system_status_report);

	ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
					   (u8 *)cfg, len);
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve system status\n");
		return ret;
	}
	return 0;
}

static int ft260_is_interface_enabled(struct hid_device *hdev)
{
	struct ft260_get_system_status_report cfg;
	struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
	int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
	int ret;

	ret = ft260_get_system_config(hdev, &cfg);
	if (ret < 0)
		return ret;

	ft260_dbg("interface:  0x%02x\n", interface);
	ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
	ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
	ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
	ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);

	switch (cfg.chip_mode) {
	case FT260_MODE_ALL:
	case FT260_MODE_BOTH:
		if (interface == 1)
			hid_info(hdev, "uart interface is not supported\n");
		else
			ret = 1;
		break;
	case FT260_MODE_UART:
		hid_info(hdev, "uart interface is not supported\n");
		break;
	case FT260_MODE_I2C:
		ret = 1;
		break;
	}
	return ret;
}

static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   u8 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", *field);
}

static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   __le16 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

#define FT260_ATTR_SHOW(name, reptype, id, type, func)			       \
	static ssize_t name##_show(struct device *kdev,			       \
				   struct device_attribute *attr, char *buf)   \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type *field = &rep.name;				       \
		int len = sizeof(rep);					       \
									       \
		return func(hdev, id, (u8 *)&rep, len, field, buf);	       \
	}

#define FT260_SSTAT_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_system_status_report,	       \
				FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)

#define FT260_I2CST_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,	       \
				FT260_I2C_STATUS, __le16, ft260_word_show)

#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func)	       \
	static ssize_t name##_store(struct device *kdev,		       \
				    struct device_attribute *attr,	       \
				    const char *buf, size_t count)	       \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type name;						       \
		int ret;						       \
									       \
		if (!func(buf, 10, (ctype *)&name)) {			       \
			rep.name = name;				       \
			rep.report = id;				       \
			rep.request = req;				       \
			ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
							   sizeof(rep));       \
			if (!ret)					       \
				ret = count;				       \
		} else {						       \
			ret = -EINVAL;					       \
		}							       \
		return ret;						       \
	}

#define FT260_BYTE_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 u8, u8, kstrtou8)

#define FT260_WORD_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 __le16, u16, kstrtou16)

FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);

FT260_SSTAT_ATTR_SHOW(pwren_status);
static DEVICE_ATTR_RO(pwren_status);

FT260_SSTAT_ATTR_SHOW(suspend_status);
static DEVICE_ATTR_RO(suspend_status);

FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
static DEVICE_ATTR_RO(hid_over_i2c_en);

FT260_SSTAT_ATTR_SHOW(power_saving_en);
static DEVICE_ATTR_RO(power_saving_en);

FT260_SSTAT_ATTR_SHOW(i2c_enable);
FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
		      FT260_SET_I2C_MODE);
static DEVICE_ATTR_RW(i2c_enable);

FT260_SSTAT_ATTR_SHOW(uart_mode);
FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
		      FT260_SET_UART_MODE);
static DEVICE_ATTR_RW(uart_mode);

FT260_SSTAT_ATTR_SHOW(clock_ctl);
FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
		      FT260_SET_CLOCK);
static DEVICE_ATTR_RW(clock_ctl);

FT260_I2CST_ATTR_SHOW(clock);
FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
		      FT260_SET_I2C_CLOCK_SPEED);
static DEVICE_ATTR_RW(clock);

static ssize_t i2c_reset_store(struct device *kdev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	struct hid_device *hdev = to_hid_device(kdev);
	int ret = ft260_i2c_reset(hdev);

	if (ret)
		return ret;
	return count;
}
static DEVICE_ATTR_WO(i2c_reset);

static const struct attribute_group ft260_attr_group = {
	.attrs = (struct attribute *[]) {
		  &dev_attr_chip_mode.attr,
		  &dev_attr_pwren_status.attr,
		  &dev_attr_suspend_status.attr,
		  &dev_attr_hid_over_i2c_en.attr,
		  &dev_attr_power_saving_en.attr,
		  &dev_attr_i2c_enable.attr,
		  &dev_attr_uart_mode.attr,
		  &dev_attr_clock_ctl.attr,
		  &dev_attr_i2c_reset.attr,
		  &dev_attr_clock.attr,
		  NULL
	}
};

static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	struct ft260_device *dev;
	struct ft260_get_chip_version_report version;
	int ret;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	ret = hid_parse(hdev);
	if (ret) {
		hid_err(hdev, "failed to parse HID\n");
		return ret;
	}

	ret = hid_hw_start(hdev, 0);
	if (ret) {
		hid_err(hdev, "failed to start HID HW\n");
		return ret;
	}

	ret = hid_hw_open(hdev);
	if (ret) {
		hid_err(hdev, "failed to open HID HW\n");
		goto err_hid_stop;
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
					   (u8 *)&version, sizeof(version));
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve chip version\n");
		goto err_hid_close;
	}

	hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
		 version.chip_code[0], version.chip_code[1],
		 version.chip_code[2], version.chip_code[3]);

	ret = ft260_is_interface_enabled(hdev);
	if (ret <= 0)
		goto err_hid_close;

	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
		hdev->version >> 8, hdev->version & 0xff, hdev->name,
		hdev->phys);

	hid_set_drvdata(hdev, dev);
	dev->hdev = hdev;
	dev->adap.owner = THIS_MODULE;
	dev->adap.class = I2C_CLASS_HWMON;
	dev->adap.algo = &ft260_i2c_algo;
	dev->adap.quirks = &ft260_i2c_quirks;
	dev->adap.dev.parent = &hdev->dev;
	snprintf(dev->adap.name, sizeof(dev->adap.name),
		 "FT260 usb-i2c bridge");

	mutex_init(&dev->lock);
	init_completion(&dev->wait);

	ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
	if (ret)
		ft260_i2c_reset(hdev);

	i2c_set_adapdata(&dev->adap, dev);
	ret = i2c_add_adapter(&dev->adap);
	if (ret) {
		hid_err(hdev, "failed to add i2c adapter\n");
		goto err_hid_close;
	}

	ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
	if (ret < 0) {
		hid_err(hdev, "failed to create sysfs attrs\n");
		goto err_i2c_free;
	}

	return 0;

err_i2c_free:
	i2c_del_adapter(&dev->adap);
err_hid_close:
	hid_hw_close(hdev);
err_hid_stop:
	hid_hw_stop(hdev);
	return ret;
}

static void ft260_remove(struct hid_device *hdev)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);

	if (!dev)
		return;

	sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
	i2c_del_adapter(&dev->adap);

	hid_hw_close(hdev);
	hid_hw_stop(hdev);
}

static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
			   u8 *data, int size)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);
	struct ft260_i2c_input_report *xfer = (void *)data;

	if (xfer->report >= FT260_I2C_REPORT_MIN &&
	    xfer->report <= FT260_I2C_REPORT_MAX) {
		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
			  xfer->length);

		if ((dev->read_buf == NULL) ||
		    (xfer->length > dev->read_len - dev->read_idx)) {
			hid_err(hdev, "unexpected report %#02x, length %d\n",
				xfer->report, xfer->length);
			return -1;
		}

		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
		       xfer->length);
		dev->read_idx += xfer->length;

		if (dev->read_idx == dev->read_len)
			complete(&dev->wait);

	} else {
		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
	}
	return 0;
}

static struct hid_driver ft260_driver = {
	.name		= "ft260",
	.id_table	= ft260_devices,
	.probe		= ft260_probe,
	.remove		= ft260_remove,
	.raw_event	= ft260_raw_event,
};

module_hid_driver(ft260_driver);
MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
MODULE_LICENSE("GPL v2");
]

23/2 2026/01/29 19:30 llm debugger
1m Model:
gemini-3-pro-preview

Tokens:
							input: 34539
							output: 674
							thoughts: 6829
						

Thoughts:
**KASAN Stack Out-of-Bounds Read in ft260 Driver**

Based on my analysis of the provided code and crash report, the root cause is a stack buffer overflow vulnerability in the `ft260` driver, specifically within the `ft260_smbus_xfer` function, located in `drivers/hid/hid-ft260.c`.

The crash report indicates a "Read of size 42" which points to a `memcpy` operation in `ft260_smbus_write` (line 486). This function copies data from a source buffer (`data`) into a destination buffer (`rep->data`). The `data` buffer's content is derived from a `union i2c_smbus_data` structure, allocated on the stack within `i2cdev_ioctl_smbus`. This stack structure has a fixed size of 34 bytes (determined by `I2C_SMBUS_BLOCK_MAX + 2`).

The vulnerability lies in the `ft260_smbus_xfer` function. It retrieves the data length from the `data->block[0]` field without validating its size against the maximum allowed block size, which is `I2C_SMBUS_BLOCK_MAX` (32) defined in `include/uapi/linux/i2c.h`. In the `I2C_SMBUS_I2C_BLOCK_DATA` case (line 725), this length value is used to determine the number of bytes to copy from `data->block + 1` into the driver's internal buffer via `ft260_smbus_write`. The crash report's "Read of size 42" combined with the KASAN report's address offset points to the `I2C_SMBUS_I2C_BLOCK_DATA` path and confirms the issue by showing a read starting at offset 1 (`...d21`) of the `temp` buffer.

If the user-provided length in `data->block[0]` is greater than 32 (e.g., 42), `ft260_smbus_write` attempts to copy 42 bytes from `data->block + 1`, which starts with the second byte of the stack-allocated `i2c_smbus_data` structure and accesses memory beyond its bounds, leading to the reported KASAN stack buffer overflow.

The fix is straightforward: Within `ft260_smbus_xfer`, before using `data->block[0]` to determine the data length for either the `I2C_SMBUS_BLOCK_DATA` or the `I2C_SMBUS_I2C_BLOCK_DATA` cases, add a check to ensure `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX`. Specifically, the following check should be added:

```c
if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
    return -EINVAL;
```
This validation should be placed just before the `ret = ft260_smbus_write(...)` calls in both affected cases (lines 709 and 725 in the provided code). This prevents out-of-bounds reads and ensures the driver only processes valid, safe data block lengths.



24/1 2026/01/29 19:31 action kernel-scratch-checkouter
0m Results:
map[KernelScratchSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/tmp/tmp3203490433]

25/1 2026/01/29 19:32 loop
13m
26/2 2026/01/29 19:32 iteration 0
13m
27/3 2026/01/29 19:32 agent patch-generator
2m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with creating a fix for a kernel bug.
Use the codeedit tool to do code edits.
Note: you will not see your changes when looking at the code using codesearch tools.
Your final reply should contain explanation of what you did in the patch and why.



Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

CPU: 0 UID: 0 PID: 6009 Comm: syz-executor165 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1925b454f9
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffee7bc0ab8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffee7bc0c48 RCX: 00007f1925b454f9
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffee7bc0ae0
R13: 00007ffee7bc0ad0 R14: 00007f1925bd8c40 R15: 0000000000000000
 </TASK>

The buggy address belongs to stack of task syz-executor165/6009
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90002410000 allocated at copy_process+0x4e7/0x3950 kernel/fork.c:2052
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x213ff
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000084ffc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 6007, tgid 6007 (syz-executor165), ts 90494270799, free_ts 87604917166
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5954 tgid 5954 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 getname_maybe_null include/linux/fs.h:2505 [inline]
 vfs_fstatat+0x43/0x170 fs/stat.c:370
 __do_sys_newfstatat fs/stat.c:542 [inline]
 __se_sys_newfstatat fs/stat.c:536 [inline]
 __x64_sys_newfstatat+0x116/0x190 fs/stat.c:536
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002417c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002417c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002417d00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002417d80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002417e00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a missing validation check in the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`.

When handling `I2C_SMBUS_BLOCK_DATA` or `I2C_SMBUS_I2C_BLOCK_DATA` commands, the driver uses the block length provided by the user (in `data->block[0]`) to determine how many bytes to read from the `data->block` buffer. However, it fails to ensure that this length does not exceed the maximum size of the `union i2c_smbus_data` structure, specifically `I2C_SMBUS_BLOCK_MAX` (32 bytes).

The crash flow is as follows:
1.  **User Input**: The `i2cdev_ioctl_smbus` function copies a `union i2c_smbus_data` from user space into a local stack variable `temp` (size 34 bytes). The user can set `temp.block[0]` to a large value, e.g., 42.
2.  **Driver Call**: `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which invokes `ft260_smbus_xfer` passing a pointer to `temp` as `data`.
3.  **Vulnerable Code**: In `ft260_smbus_xfer`, specifically for the `I2C_SMBUS_I2C_BLOCK_DATA` case (indicated by the KASAN report showing the read starting at offset 1 of the object):
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        // ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
    It passes `data->block + 1` as the source and `data->block[0]` (42) as the length.
4.  **Out-of-Bounds Read**: `ft260_smbus_write` performs a `memcpy` reading 42 bytes from `data->block + 1`. Since `temp` is only 34 bytes large, reading 42 bytes starting from offset 1 extends past the end of the stack-allocated structure, triggering the KASAN stack-out-of-bounds error.

**Fix**:
Add a check in `ft260_smbus_xfer` to validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX` before processing block transfers.

```c
	case I2C_SMBUS_BLOCK_DATA:
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
			return -EINVAL;
        // ...
```



Reply:
The bug was caused by a missing validation check in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`. When handling `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`, the function uses `data->block[0]` as the length for the transfer without checking if it exceeds the maximum allowed block size (`I2C_SMBUS_BLOCK_MAX`).

A malicious or buggy user space program can pass a large value (e.g., 42) in `data->block[0]` via `ioctl`. This leads to `ft260_smbus_write` (for writes) or `ft260_i2c_read` (for reads) accessing the `data->block` buffer beyond its bounds. The `data` buffer is allocated on the stack (as `union i2c_smbus_data` in `i2cdev_ioctl_smbus`), so this results in a stack-out-of-bounds access.

The fix involves adding a check in `ft260_smbus_xfer` to ensure `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX` (32) before proceeding with the block transfer.

```c
// drivers/hid/hid-ft260.c

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
    // ...
	switch (size) {
    // ...
	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
    // ...
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
    // ...
```

28/4 2026/01/29 19:32 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 6646
							output: 27
							thoughts: 198
						

Thoughts:
**Fixing the KASAN Error in hid-ft260.c**

Alright, let's tackle this KASAN stack-out-of-bounds issue in `drivers/hid/hid-ft260.c`. The explanation is pretty clear; it looks like `ft260_smbus_xfer` is the culprit. It's using `data->block[0]` as the transfer length, but it's *not* validating it for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transfers. This, of course, then leads to `ft260_smbus_write` attempting to read beyond the buffer's bounds if `data->block[0]` exceeds `I2C_SMBUS_BLOCK_MAX`. Classic.

My next step is straightforward: I need to pinpoint the `ft260_smbus_xfer` function within `drivers/hid/hid-ft260.c` itself. Once I've got the code in front of me, I'll add the necessary check. I'll need to confirm that `data->block[0]` is within the bounds of `I2C_SMBUS_BLOCK_MAX` before proceeding with the transfer, probably using an `if` statement to short-circuit the function or return an error if it's too big. So, first things first, let's open `hid-ft260.c` and dive in.



29/4 2026/01/29 19:32 tool read-file
0m Args:
map[File:drivers/hid/hid-ft260.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
 * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
 *
 * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
 *
 * Data Sheet:
 *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
 */

#include "hid-ids.h"
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/usb.h>

#ifdef DEBUG
static int ft260_debug = 1;
#else
static int ft260_debug;
#endif
module_param_named(debug, ft260_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");

#define ft260_dbg(format, arg...)					  \
	do {								  \
		if (ft260_debug)					  \
			pr_info("%s: " format, __func__, ##arg);	  \
	} while (0)

#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)

#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */

/*
 * The ft260 input report format defines 62 bytes for the data payload, but
 * when requested 62 bytes, the controller returns 60 and 2 in separate input
 * reports. To achieve better performance with the multi-report read data
 * transfers, we set the maximum read payload length to a multiple of 60.
 * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
 * which is excessive; On the other hand, some higher layer drivers like at24
 * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
 * of I2C for potentially troublesome amounts of time, we select the maximum
 * read payload length to be 180 bytes.
*/
#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)

/*
 * Device interface configuration.
 * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
 * First implementes USB HID to I2C bridge function and
 * second - USB HID to UART bridge function.
 */
enum {
	FT260_MODE_ALL			= 0x00,
	FT260_MODE_I2C			= 0x01,
	FT260_MODE_UART			= 0x02,
	FT260_MODE_BOTH			= 0x03,
};

/* Control pipe */
enum {
	FT260_GET_RQST_TYPE		= 0xA1,
	FT260_GET_REPORT		= 0x01,
	FT260_SET_RQST_TYPE		= 0x21,
	FT260_SET_REPORT		= 0x09,
	FT260_FEATURE			= 0x03,
};

/* Report IDs / Feature In */
enum {
	FT260_CHIP_VERSION		= 0xA0,
	FT260_SYSTEM_SETTINGS		= 0xA1,
	FT260_I2C_STATUS		= 0xC0,
	FT260_I2C_READ_REQ		= 0xC2,
	FT260_I2C_REPORT_MIN		= 0xD0,
	FT260_I2C_REPORT_MAX		= 0xDE,
	FT260_GPIO			= 0xB0,
	FT260_UART_INTERRUPT_STATUS	= 0xB1,
	FT260_UART_STATUS		= 0xE0,
	FT260_UART_RI_DCD_STATUS	= 0xE1,
	FT260_UART_REPORT		= 0xF0,
};

/* Feature Out */
enum {
	FT260_SET_CLOCK			= 0x01,
	FT260_SET_I2C_MODE		= 0x02,
	FT260_SET_UART_MODE		= 0x03,
	FT260_ENABLE_INTERRUPT		= 0x05,
	FT260_SELECT_GPIO2_FUNC		= 0x06,
	FT260_ENABLE_UART_DCD_RI	= 0x07,
	FT260_SELECT_GPIOA_FUNC		= 0x08,
	FT260_SELECT_GPIOG_FUNC		= 0x09,
	FT260_SET_INTERRUPT_TRIGGER	= 0x0A,
	FT260_SET_SUSPEND_OUT_POLAR	= 0x0B,
	FT260_ENABLE_UART_RI_WAKEUP	= 0x0C,
	FT260_SET_UART_RI_WAKEUP_CFG	= 0x0D,
	FT260_SET_I2C_RESET		= 0x20,
	FT260_SET_I2C_CLOCK_SPEED	= 0x22,
	FT260_SET_UART_RESET		= 0x40,
	FT260_SET_UART_CONFIG		= 0x41,
	FT260_SET_UART_BAUD_RATE	= 0x42,
	FT260_SET_UART_DATA_BIT		= 0x43,
	FT260_SET_UART_PARITY		= 0x44,
	FT260_SET_UART_STOP_BIT		= 0x45,
	FT260_SET_UART_BREAKING		= 0x46,
	FT260_SET_UART_XON_XOFF		= 0x49,
};

/* Response codes in I2C status report */
enum {
	FT260_I2C_STATUS_SUCCESS	= 0x00,
	FT260_I2C_STATUS_CTRL_BUSY	= 0x01,
	FT260_I2C_STATUS_ERROR		= 0x02,
	FT260_I2C_STATUS_ADDR_NO_ACK	= 0x04,
	FT260_I2C_STATUS_DATA_NO_ACK	= 0x08,
	FT260_I2C_STATUS_ARBITR_LOST	= 0x10,
	FT260_I2C_STATUS_CTRL_IDLE	= 0x20,
	FT260_I2C_STATUS_BUS_BUSY	= 0x40,
};

/* I2C Conditions flags */
enum {
	FT260_FLAG_NONE			= 0x00,
	FT260_FLAG_START		= 0x02,
	FT260_FLAG_START_REPEATED	= 0x03,
	FT260_FLAG_STOP			= 0x04,
	FT260_FLAG_START_STOP		= 0x06,
	FT260_FLAG_START_STOP_REPEATED	= 0x07,
};

#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)

/* Feature In reports */

struct ft260_get_chip_version_report {
	u8 report;		/* FT260_CHIP_VERSION */
	u8 chip_code[4];	/* FTDI chip identification code */
	u8 reserved[8];
} __packed;

struct ft260_get_system_status_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 chip_mode;		/* DCNF0 and DCNF1 status, bits 0-1 */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
	u8 suspend_status;	/* 0 - not suspended, 1 - suspended */
	u8 pwren_status;	/* 0 - FT260 is not ready, 1 - ready */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
	u8 hid_over_i2c_en;	/* 0 - disabled, 1 - enabled */
	u8 gpio2_function;	/* 0 - GPIO,  1 - SUSPOUT, */
				/* 2 - PWREN, 4 - TX_LED */
	u8 gpioA_function;	/* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
	u8 gpioG_function;	/* 0 - GPIO, 2 - PWREN, */
				/* 5 - RX_LED, 6 - BCD_DET */
	u8 suspend_out_pol;	/* 0 - active-high, 1 - active-low */
	u8 enable_wakeup_int;	/* 0 - disabled, 1 - enabled */
	u8 intr_cond;		/* Interrupt trigger conditions */
	u8 power_saving_en;	/* 0 - disabled, 1 - enabled */
	u8 reserved[10];
} __packed;

struct ft260_get_i2c_status_report {
	u8 report;		/* FT260_I2C_STATUS */
	u8 bus_status;		/* I2C bus status */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
	u8 reserved;
} __packed;

/* Feature Out reports */

struct ft260_set_system_clock_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_CLOCK */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
} __packed;

struct ft260_set_i2c_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_MODE */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
} __packed;

struct ft260_set_uart_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_UART_MODE */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

struct ft260_set_i2c_reset_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_RESET */
} __packed;

struct ft260_set_i2c_speed_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_CLOCK_SPEED */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
} __packed;

/* Data transfer reports */

struct ft260_i2c_write_request_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	u8 length;		/* data payload length */
	u8 data[FT260_WR_DATA_MAX]; /* data payload */
} __packed;

struct ft260_i2c_read_request_report {
	u8 report;		/* FT260_I2C_READ_REQ */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	__le16 length;		/* data payload length */
} __packed;

struct ft260_i2c_input_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 length;		/* data payload length */
	u8 data[2];		/* data payload */
} __packed;

static const struct hid_device_id ft260_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
			 USB_DEVICE_ID_FT260) },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(hid, ft260_devices);

struct ft260_device {
	struct i2c_adapter adap;
	struct hid_device *hdev;
	struct completion wait;
	struct mutex lock;
	u8 write_buf[FT260_REPORT_MAX_LENGTH];
	unsigned long need_wakeup_at;
	u8 *read_buf;
	u16 read_idx;
	u16 read_len;
	u16 clock;
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
					unsigned char report_id, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmalloc(len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
				 HID_REQ_GET_REPORT);
	if (likely(ret == len))
		memcpy(data, buf, len);
	else if (ret >= 0)
		ret = -EIO;
	kfree(buf);
	return ret;
}

static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	buf[0] = FT260_SYSTEM_SETTINGS;

	ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
				 HID_REQ_SET_REPORT);

	kfree(buf);
	return ret;
}

static int ft260_i2c_reset(struct hid_device *hdev)
{
	struct ft260_set_i2c_reset_report report;
	int ret;

	report.request = FT260_SET_I2C_RESET;

	ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
	if (ret < 0) {
		hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
		return ret;
	}

	ft260_dbg("done\n");
	return ret;
}

static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
	struct hid_device *hdev = dev->hdev;
	struct ft260_get_i2c_status_report report;
	int ret;

	if (time_is_before_jiffies(dev->need_wakeup_at)) {
		ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
						(u8 *)&report, sizeof(report));
		if (unlikely(ret < 0)) {
			hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
				ret);
		} else {
			dev->need_wakeup_at = jiffies +
				msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
			ft260_dbg("bus_status %#02x, wakeup\n",
				  report.bus_status);
		}
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
					   (u8 *)&report, sizeof(report));
	if (unlikely(ret < 0)) {
		hid_err(hdev, "failed to retrieve status: %d\n", ret);
		return ret;
	}

	dev->clock = le16_to_cpu(report.clock);
	ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
		  dev->clock);

	if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
		return -EAGAIN;

	/*
	 * The error condition (bit 1) is a status bit reflecting any
	 * error conditions. When any of the bits 2, 3, or 4 are raised
	 * to 1, bit 1 is also set to 1.
	 */
	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
		return -EIO;
	}

	return 0;
}

static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
				   size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_output_report(hdev, buf, len);

	kfree(buf);
	return ret;
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
						u8 *data, int len)
{
	u8 bus_busy;
	int ret, usec, try = 100;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)data;

	ret = ft260_hid_output_report(hdev, data, len);
	if (ret < 0) {
		hid_err(hdev, "%s: failed to start transfer, ret %d\n",
			__func__, ret);
		ft260_i2c_reset(hdev);
		return ret;
	}

	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
	usec = len * 9000 / dev->clock;
	if (usec > 2000) {
		usec -= 1500;
		usleep_range(usec, usec + 100);
		ft260_dbg("wait %d usec, len %d\n", usec, len);
	}

	/*
	 * Do not check the busy bit for combined transactions
	 * since the controller keeps the bus busy between writing
	 * and reading IOs to ensure an atomic operation.
	 */
	if (rep->flag == FT260_FLAG_START)
		bus_busy = 0;
	else
		bus_busy = FT260_I2C_STATUS_BUS_BUSY;

	do {
		ret = ft260_xfer_status(dev, bus_busy);
		if (ret != -EAGAIN)
			break;
	} while (--try);

	if (ret == 0)
		return 0;

	ft260_i2c_reset(hdev);
	return -EIO;
}

static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
			   int len, u8 flag)
{
	int ret, wr_len, idx = 0;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (len < 1)
		return -EINVAL;

	rep->flag = FT260_FLAG_START;

	do {
		if (len <= FT260_WR_DATA_MAX) {
			wr_len = len;
			if (flag == FT260_FLAG_START_STOP)
				rep->flag |= FT260_FLAG_STOP;
		} else {
			wr_len = FT260_WR_DATA_MAX;
		}

		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
		rep->address = addr;
		rep->length = wr_len;

		memcpy(rep->data, &data[idx], wr_len);

		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
			  rep->report, addr, idx, len, wr_len,
			  rep->flag, data[0]);

		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
							   wr_len + 4);
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			return ret;
		}

		len -= wr_len;
		idx += wr_len;
		rep->flag = 0;

	} while (len > 0);

	return 0;
}

static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}

static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}

/*
 * A random read operation is implemented as a dummy write operation, followed
 * by a current address read operation. The dummy write operation is used to
 * load the target byte address into the current byte address counter, from
 * which the subsequent current address read operation then reads.
 */
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
	int ret;
	int wr_len = msgs[0].len;
	int rd_len = msgs[1].len;
	struct hid_device *hdev = dev->hdev;
	u8 addr = msgs[0].addr;
	u16 read_off = 0;

	if (wr_len > 2) {
		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
		return -EOPNOTSUPP;
	}

	if (ft260_debug) {
		if (wr_len == 2)
			read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
		else
			read_off = *msgs[0].buf;

		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
			read_off, rd_len, wr_len);
	}

	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
			      FT260_FLAG_START);
	if (ret < 0)
		return ret;

	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
			     FT260_FLAG_START_STOP_REPEATED);
	if (ret < 0)
		return ret;

	return 0;
}

static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
			  int num)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	if (num == 1) {
		if (msgs->flags & I2C_M_RD)
			ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
					     msgs->len, FT260_FLAG_START_STOP);
		else
			ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
					      msgs->len, FT260_FLAG_START_STOP);
		if (ret < 0)
			goto i2c_exit;

	} else {
		/* Combined write then read message */
		ret = ft260_i2c_write_read(dev, msgs);
		if (ret < 0)
			goto i2c_exit;
	}

	ret = num;
i2c_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	ft260_dbg("smbus size %d\n", size);

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "power management error: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	switch (size) {
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
		break;
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd,
						(u8 *)&data->word, 2,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
	default:
		hid_err(hdev, "unsupported smbus transaction size %d\n", size);
		ret = -EOPNOTSUPP;
	}

smbus_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static u32 ft260_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

static const struct i2c_adapter_quirks ft260_i2c_quirks = {
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
	.max_comb_1st_msg_len = 2,
};

static const struct i2c_algorithm ft260_i2c_algo = {
	.master_xfer = ft260_i2c_xfer,
	.smbus_xfer = ft260_smbus_xfer,
	.functionality = ft260_functionality,
};

static int ft260_get_system_config(struct hid_device *hdev,
				   struct ft260_get_system_status_report *cfg)
{
	int ret;
	int len = sizeof(struct ft260_get_system_status_report);

	ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
					   (u8 *)cfg, len);
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve system status\n");
		return ret;
	}
	return 0;
}

static int ft260_is_interface_enabled(struct hid_device *hdev)
{
	struct ft260_get_system_status_report cfg;
	struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
	int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
	int ret;

	ret = ft260_get_system_config(hdev, &cfg);
	if (ret < 0)
		return ret;

	ft260_dbg("interface:  0x%02x\n", interface);
	ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
	ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
	ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
	ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);

	switch (cfg.chip_mode) {
	case FT260_MODE_ALL:
	case FT260_MODE_BOTH:
		if (interface == 1)
			hid_info(hdev, "uart interface is not supported\n");
		else
			ret = 1;
		break;
	case FT260_MODE_UART:
		hid_info(hdev, "uart interface is not supported\n");
		break;
	case FT260_MODE_I2C:
		ret = 1;
		break;
	}
	return ret;
}

static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   u8 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", *field);
}

static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   __le16 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

#define FT260_ATTR_SHOW(name, reptype, id, type, func)			       \
	static ssize_t name##_show(struct device *kdev,			       \
				   struct device_attribute *attr, char *buf)   \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type *field = &rep.name;				       \
		int len = sizeof(rep);					       \
									       \
		return func(hdev, id, (u8 *)&rep, len, field, buf);	       \
	}

#define FT260_SSTAT_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_system_status_report,	       \
				FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)

#define FT260_I2CST_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,	       \
				FT260_I2C_STATUS, __le16, ft260_word_show)

#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func)	       \
	static ssize_t name##_store(struct device *kdev,		       \
				    struct device_attribute *attr,	       \
				    const char *buf, size_t count)	       \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type name;						       \
		int ret;						       \
									       \
		if (!func(buf, 10, (ctype *)&name)) {			       \
			rep.name = name;				       \
			rep.report = id;				       \
			rep.request = req;				       \
			ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
							   sizeof(rep));       \
			if (!ret)					       \
				ret = count;				       \
		} else {						       \
			ret = -EINVAL;					       \
		}							       \
		return ret;						       \
	}

#define FT260_BYTE_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 u8, u8, kstrtou8)

#define FT260_WORD_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 __le16, u16, kstrtou16)

FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);

FT260_SSTAT_ATTR_SHOW(pwren_status);
static DEVICE_ATTR_RO(pwren_status);

FT260_SSTAT_ATTR_SHOW(suspend_status);
static DEVICE_ATTR_RO(suspend_status);

FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
static DEVICE_ATTR_RO(hid_over_i2c_en);

FT260_SSTAT_ATTR_SHOW(power_saving_en);
static DEVICE_ATTR_RO(power_saving_en);

FT260_SSTAT_ATTR_SHOW(i2c_enable);
FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
		      FT260_SET_I2C_MODE);
static DEVICE_ATTR_RW(i2c_enable);

FT260_SSTAT_ATTR_SHOW(uart_mode);
FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
		      FT260_SET_UART_MODE);
static DEVICE_ATTR_RW(uart_mode);

FT260_SSTAT_ATTR_SHOW(clock_ctl);
FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
		      FT260_SET_CLOCK);
static DEVICE_ATTR_RW(clock_ctl);

FT260_I2CST_ATTR_SHOW(clock);
FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
		      FT260_SET_I2C_CLOCK_SPEED);
static DEVICE_ATTR_RW(clock);

static ssize_t i2c_reset_store(struct device *kdev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	struct hid_device *hdev = to_hid_device(kdev);
	int ret = ft260_i2c_reset(hdev);

	if (ret)
		return ret;
	return count;
}
static DEVICE_ATTR_WO(i2c_reset);

static const struct attribute_group ft260_attr_group = {
	.attrs = (struct attribute *[]) {
		  &dev_attr_chip_mode.attr,
		  &dev_attr_pwren_status.attr,
		  &dev_attr_suspend_status.attr,
		  &dev_attr_hid_over_i2c_en.attr,
		  &dev_attr_power_saving_en.attr,
		  &dev_attr_i2c_enable.attr,
		  &dev_attr_uart_mode.attr,
		  &dev_attr_clock_ctl.attr,
		  &dev_attr_i2c_reset.attr,
		  &dev_attr_clock.attr,
		  NULL
	}
};

static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	struct ft260_device *dev;
	struct ft260_get_chip_version_report version;
	int ret;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	ret = hid_parse(hdev);
	if (ret) {
		hid_err(hdev, "failed to parse HID\n");
		return ret;
	}

	ret = hid_hw_start(hdev, 0);
	if (ret) {
		hid_err(hdev, "failed to start HID HW\n");
		return ret;
	}

	ret = hid_hw_open(hdev);
	if (ret) {
		hid_err(hdev, "failed to open HID HW\n");
		goto err_hid_stop;
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
					   (u8 *)&version, sizeof(version));
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve chip version\n");
		goto err_hid_close;
	}

	hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
		 version.chip_code[0], version.chip_code[1],
		 version.chip_code[2], version.chip_code[3]);

	ret = ft260_is_interface_enabled(hdev);
	if (ret <= 0)
		goto err_hid_close;

	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
		hdev->version >> 8, hdev->version & 0xff, hdev->name,
		hdev->phys);

	hid_set_drvdata(hdev, dev);
	dev->hdev = hdev;
	dev->adap.owner = THIS_MODULE;
	dev->adap.class = I2C_CLASS_HWMON;
	dev->adap.algo = &ft260_i2c_algo;
	dev->adap.quirks = &ft260_i2c_quirks;
	dev->adap.dev.parent = &hdev->dev;
	snprintf(dev->adap.name, sizeof(dev->adap.name),
		 "FT260 usb-i2c bridge");

	mutex_init(&dev->lock);
	init_completion(&dev->wait);

	ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
	if (ret)
		ft260_i2c_reset(hdev);

	i2c_set_adapdata(&dev->adap, dev);
	ret = i2c_add_adapter(&dev->adap);
	if (ret) {
		hid_err(hdev, "failed to add i2c adapter\n");
		goto err_hid_close;
	}

	ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
	if (ret < 0) {
		hid_err(hdev, "failed to create sysfs attrs\n");
		goto err_i2c_free;
	}

	return 0;

err_i2c_free:
	i2c_del_adapter(&dev->adap);
err_hid_close:
	hid_hw_close(hdev);
err_hid_stop:
	hid_hw_stop(hdev);
	return ret;
}

static void ft260_remove(struct hid_device *hdev)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);

	if (!dev)
		return;

	sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
	i2c_del_adapter(&dev->adap);

	hid_hw_close(hdev);
	hid_hw_stop(hdev);
}

static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
			   u8 *data, int size)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);
	struct ft260_i2c_input_report *xfer = (void *)data;

	if (xfer->report >= FT260_I2C_REPORT_MIN &&
	    xfer->report <= FT260_I2C_REPORT_MAX) {
		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
			  xfer->length);

		if ((dev->read_buf == NULL) ||
		    (xfer->length > dev->read_len - dev->read_idx)) {
			hid_err(hdev, "unexpected report %#02x, length %d\n",
				xfer->report, xfer->length);
			return -1;
		}

		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
		       xfer->length);
		dev->read_idx += xfer->length;

		if (dev->read_idx == dev->read_len)
			complete(&dev->wait);

	} else {
		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
	}
	return 0;
}

static struct hid_driver ft260_driver = {
	.name		= "ft260",
	.id_table	= ft260_devices,
	.probe		= ft260_probe,
	.remove		= ft260_remove,
	.raw_event	= ft260_raw_event,
};

module_hid_driver(ft260_driver);
MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
MODULE_LICENSE("GPL v2");
]

30/4 2026/01/29 19:32 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 20052
							output: 28
							thoughts: 3209
						

Thoughts:
**Analysis of Potential SMBus Out-of-Bounds Write in ft260 Driver**

Alright, I've got the `ft260_smbus_xfer` function in front of me, along with the KASAN report highlighting a potential out-of-bounds write in `ft260_smbus_write`.  My initial thought is to trace the execution path and identify how the length parameter is derived and used.

First, I see the function `ft260_smbus_xfer` uses `ft260_smbus_write` and also `ft260_i2c_read`. The KASAN report points to the `memcpy` in `ft260_smbus_write` as the culprit. I know that `ft260_smbus_write` copies data from a user-provided buffer to an internal buffer. The length used for the copy operation (`data_len`) is the issue.

I quickly check the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases in the `switch` statement. In both cases, the `data->block[0]` value is used to determine how many bytes to write or read. My immediate concern is that `data->block[0]` isn't validated and could easily exceed the size of the target buffer. Looking at the definitions in `i2c.h` and the bug report's indication of a 34-byte `temp` object, I confirm that the `union i2c_smbus_data` has a `block` member of 34 bytes (32 for data plus 2 for length and compatibility).

Now, if `data->block[0]` is, say, 42, the code in `ft260_smbus_write` would read 43 bytes from the provided data buffer, which starts at `data->block` for `I2C_SMBUS_BLOCK_DATA`, or at `data->block + 1` for `I2C_SMBUS_I2C_BLOCK_DATA`. This will lead to an out-of-bounds read and write on `memcpy`. The key is that `data->block[0]` is directly used as a length without any bounds check.

So, I need to add a check for `data->block[0]` to make sure its value does not exceed `I2C_SMBUS_BLOCK_MAX` (32).

I immediately begin to write a fix, adding the check and a return of `-EINVAL` if the length is invalid, for both the read and write cases in the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`.

However, as I start to write the fix, the read path also uses `data->block[0]`.

For `I2C_SMBUS_BLOCK_DATA` reads, the code calls `ft260_i2c_read`, passing `data->block[0] + 1` as the length. This also raises a red flag.  The length passed to `ft260_i2c_read` can be controlled by user-provided length `data->block[0]`. If this length is large enough, the write inside `ft260_i2c_read` could overflow `data->block`.

So, it seems that there is a problem with how `ft260` treats `I2C_SMBUS_BLOCK_DATA` in read as well. This is strange because `I2C_SMBUS_BLOCK_DATA` usually is that the block length is received from the device. However, `ft260` actually reads `data->block[0]` bytes from the device for the read case.

I check `I2C_SMBUS_I2C_BLOCK_DATA` again. This case is more obvious.  `data->block[0]` directly defines the length of the read/write. If the length comes from user-space, a check is needed.

I quickly double-check where `data->block[0]` is initialized to confirm if this value comes from the user.  I need to trace the `i2cdev_ioctl_smbus` implementation in `i2c-dev.c`, to ensure the proper check is performed. It turns out that `i2cdev_ioctl_smbus` copies user data into a temporary buffer for both read and write, if the operation is either of  `I2C_SMBUS_I2C_BLOCK_DATA`, `I2C_SMBUS_BLOCK_DATA_


31/4 2026/01/29 19:32 tool read-file
0m Args:
map[File:drivers/i2c/i2c-dev.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-or-later
/*
    i2c-dev.c - i2c-bus driver, char device interface

    Copyright (C) 1995-97 Simon G. Vogl
    Copyright (C) 1998-99 Frodo Looijaard <frodol@dds.nl>
    Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>

*/

/* Note that this is a complete rewrite of Simon Vogl's i2c-dev module.
   But I have used so much of his original code and ideas that it seems
   only fair to recognize him as co-author -- Frodo */

/* The I2C_RDWR ioctl code is written by Kolja Waschk <waschk@telos.de> */

#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt

#include <linux/cdev.h>
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

/*
 * An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a
 * slave (i2c_client) with which messages will be exchanged.  It's coupled
 * with a character special file which is accessed by user mode drivers.
 *
 * The list of i2c_dev structures is parallel to the i2c_adapter lists
 * maintained by the driver model, and is updated using bus notifications.
 */
struct i2c_dev {
	struct list_head list;
	struct i2c_adapter *adap;
	struct device dev;
	struct cdev cdev;
};

#define I2C_MINORS	(MINORMASK + 1)
static LIST_HEAD(i2c_dev_list);
static DEFINE_SPINLOCK(i2c_dev_list_lock);

static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
{
	struct i2c_dev *i2c_dev;

	spin_lock(&i2c_dev_list_lock);
	list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
		if (i2c_dev->adap->nr == index)
			goto found;
	}
	i2c_dev = NULL;
found:
	spin_unlock(&i2c_dev_list_lock);
	return i2c_dev;
}

static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
{
	struct i2c_dev *i2c_dev;

	if (adap->nr >= I2C_MINORS) {
		pr_err("Out of device minors (%d)\n", adap->nr);
		return ERR_PTR(-ENODEV);
	}

	i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL);
	if (!i2c_dev)
		return ERR_PTR(-ENOMEM);
	i2c_dev->adap = adap;

	spin_lock(&i2c_dev_list_lock);
	list_add_tail(&i2c_dev->list, &i2c_dev_list);
	spin_unlock(&i2c_dev_list_lock);
	return i2c_dev;
}

static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
{
	spin_lock(&i2c_dev_list_lock);
	list_del(&i2c_dev->list);
	spin_unlock(&i2c_dev_list_lock);
	if (del_cdev)
		cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
	put_device(&i2c_dev->dev);
}

static ssize_t name_show(struct device *dev,
			 struct device_attribute *attr, char *buf)
{
	struct i2c_dev *i2c_dev = i2c_dev_get_by_minor(MINOR(dev->devt));

	if (!i2c_dev)
		return -ENODEV;
	return sysfs_emit(buf, "%s\n", i2c_dev->adap->name);
}
static DEVICE_ATTR_RO(name);

static struct attribute *i2c_attrs[] = {
	&dev_attr_name.attr,
	NULL,
};
ATTRIBUTE_GROUPS(i2c);

/* ------------------------------------------------------------------------- */

/*
 * After opening an instance of this character special file, a file
 * descriptor starts out associated only with an i2c_adapter (and bus).
 *
 * Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg
 * traffic to any devices on the bus used by that adapter.  That's because
 * the i2c_msg vectors embed all the addressing information they need, and
 * are submitted directly to an i2c_adapter.  However, SMBus-only adapters
 * don't support that interface.
 *
 * To use read()/write() system calls on that file descriptor, or to use
 * SMBus interfaces (and work with SMBus-only hosts!), you must first issue
 * an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl.  That configures an anonymous
 * (never registered) i2c_client so it holds the addressing information
 * needed by those system calls and by this SMBus interface.
 */

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	struct i2c_client *client = file->private_data;

	/* Adapter must support I2C transfers */
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -EOPNOTSUPP;

	if (count > 8192)
		count = 8192;

	tmp = kzalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;

	pr_debug("i2c-%d reading %zu bytes.\n", iminor(file_inode(file)), count);

	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		if (copy_to_user(buf, tmp, ret))
			ret = -EFAULT;
	kfree(tmp);
	return ret;
}

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;

	/* Adapter must support I2C transfers */
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -EOPNOTSUPP;

	if (count > 8192)
		count = 8192;

	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-%d writing %zu bytes.\n", iminor(file_inode(file)), count);

	ret = i2c_master_send(client, tmp, count);
	kfree(tmp);
	return ret;
}

static int i2cdev_check(struct device *dev, void *addrp)
{
	struct i2c_client *client = i2c_verify_client(dev);

	if (!client || client->addr != *(unsigned int *)addrp)
		return 0;

	return dev->driver ? -EBUSY : 0;
}

/* walk up mux tree */
static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr)
{
	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
	int result;

	result = device_for_each_child(&adapter->dev, &addr, i2cdev_check);
	if (!result && parent)
		result = i2cdev_check_mux_parents(parent, addr);

	return result;
}

/* recurse down mux tree */
static int i2cdev_check_mux_children(struct device *dev, void *addrp)
{
	int result;

	if (dev->type == &i2c_adapter_type)
		result = device_for_each_child(dev, addrp,
						i2cdev_check_mux_children);
	else
		result = i2cdev_check(dev, addrp);

	return result;
}

/* This address checking function differs from the one in i2c-core
   in that it considers an address with a registered device, but no
   driver bound to it, as NOT busy. */
static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
{
	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
	int result = 0;

	if (parent)
		result = i2cdev_check_mux_parents(parent, addr);

	if (!result)
		result = device_for_each_child(&adapter->dev, &addr,
						i2cdev_check_mux_children);

	return result;
}

static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		unsigned nmsgs, struct i2c_msg *msgs)
{
	u8 __user **data_ptrs;
	int i, res;

	/* Adapter must support I2C transfers */
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -EOPNOTSUPP;

	data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
	if (!data_ptrs)
		return -ENOMEM;

	res = 0;
	for (i = 0; i < nmsgs; i++) {
		/* Limit the size of the message to a sane amount */
		if (msgs[i].len > 8192) {
			res = -EINVAL;
			break;
		}

		data_ptrs[i] = (u8 __user *)msgs[i].buf;
		msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
		if (IS_ERR(msgs[i].buf)) {
			res = PTR_ERR(msgs[i].buf);
			break;
		}
		/* memdup_user allocates with GFP_KERNEL, so DMA is ok */
		msgs[i].flags |= I2C_M_DMA_SAFE;

		/*
		 * If the message length is received from the slave (similar
		 * to SMBus block read), we must ensure that the buffer will
		 * be large enough to cope with a message length of
		 * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus
		 * drivers allow. The first byte in the buffer must be
		 * pre-filled with the number of extra bytes, which must be
		 * at least one to hold the message length, but can be
		 * greater (for example to account for a checksum byte at
		 * the end of the message.)
		 */
		if (msgs[i].flags & I2C_M_RECV_LEN) {
			if (!(msgs[i].flags & I2C_M_RD) ||
			    msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
			    msgs[i].len < msgs[i].buf[0] +
					     I2C_SMBUS_BLOCK_MAX) {
				i++;
				res = -EINVAL;
				break;
			}

			msgs[i].len = msgs[i].buf[0];
		}
	}
	if (res < 0) {
		int j;
		for (j = 0; j < i; ++j)
			kfree(msgs[j].buf);
		kfree(data_ptrs);
		return res;
	}

	res = i2c_transfer(client->adapter, msgs, nmsgs);
	while (i-- > 0) {
		if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
			if (copy_to_user(data_ptrs[i], msgs[i].buf,
					 msgs[i].len))
				res = -EFAULT;
		}
		kfree(msgs[i].buf);
	}
	kfree(data_ptrs);
	return res;
}

static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
		u8 read_write, u8 command, u32 size,
		union i2c_smbus_data __user *data)
{
	union i2c_smbus_data temp = {};
	int datasize, res;

	if ((size != I2C_SMBUS_BYTE) &&
	    (size != I2C_SMBUS_QUICK) &&
	    (size != I2C_SMBUS_BYTE_DATA) &&
	    (size != I2C_SMBUS_WORD_DATA) &&
	    (size != I2C_SMBUS_PROC_CALL) &&
	    (size != I2C_SMBUS_BLOCK_DATA) &&
	    (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
	    (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
	    (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
		dev_dbg(&client->adapter->dev,
			"size out of range (%x) in ioctl I2C_SMBUS.\n",
			size);
		return -EINVAL;
	}
	/* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
	   so the check is valid if size==I2C_SMBUS_QUICK too. */
	if ((read_write != I2C_SMBUS_READ) &&
	    (read_write != I2C_SMBUS_WRITE)) {
		dev_dbg(&client->adapter->dev,
			"read_write out of range (%x) in ioctl I2C_SMBUS.\n",
			read_write);
		return -EINVAL;
	}

	/* Note that command values are always valid! */

	if ((size == I2C_SMBUS_QUICK) ||
	    ((size == I2C_SMBUS_BYTE) &&
	    (read_write == I2C_SMBUS_WRITE)))
		/* These are special: we do not use data */
		return i2c_smbus_xfer(client->adapter, client->addr,
				      client->flags, read_write,
				      command, size, NULL);

	if (data == NULL) {
		dev_dbg(&client->adapter->dev,
			"data is NULL pointer in ioctl I2C_SMBUS.\n");
		return -EINVAL;
	}

	if ((size == I2C_SMBUS_BYTE_DATA) ||
	    (size == I2C_SMBUS_BYTE))
		datasize = sizeof(data->byte);
	else if ((size == I2C_SMBUS_WORD_DATA) ||
		 (size == I2C_SMBUS_PROC_CALL))
		datasize = sizeof(data->word);
	else /* size == smbus block, i2c block, or block proc. call */
		datasize = sizeof(data->block);

	if ((size == I2C_SMBUS_PROC_CALL) ||
	    (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
	    (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
	    (read_write == I2C_SMBUS_WRITE)) {
		if (copy_from_user(&temp, data, datasize))
			return -EFAULT;
	}
	if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
		/* Convert old I2C block commands to the new
		   convention. This preserves binary compatibility. */
		size = I2C_SMBUS_I2C_BLOCK_DATA;
		if (read_write == I2C_SMBUS_READ)
			temp.block[0] = I2C_SMBUS_BLOCK_MAX;
	}
	res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
	      read_write, command, size, &temp);
	if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
		     (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
		     (read_write == I2C_SMBUS_READ))) {
		if (copy_to_user(data, &temp, datasize))
			return -EFAULT;
	}
	return res;
}

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	case I2C_TENBIT:
		if (arg)
			client->flags |= I2C_M_TEN;
		else
			client->flags &= ~I2C_M_TEN;
		return 0;
	case I2C_PEC:
		/*
		 * Setting the PEC flag here won't affect kernel drivers,
		 * which will be using the i2c_client node registered with
		 * the driver model core.  Likewise, when that client has
		 * the PEC flag already set, the i2c-dev driver won't see
		 * (or use) this setting.
		 */
		if (arg)
			client->flags |= I2C_CLIENT_PEC;
		else
			client->flags &= ~I2C_CLIENT_PEC;
		return 0;
	case I2C_FUNCS:
		funcs = i2c_get_functionality(client->adapter);
		return put_user(funcs, (unsigned long __user *)arg);

	case I2C_RDWR: {
		struct i2c_rdwr_ioctl_data rdwr_arg;
		struct i2c_msg *rdwr_pa;
		int res;

		if (copy_from_user(&rdwr_arg,
				   (struct i2c_rdwr_ioctl_data __user *)arg,
				   sizeof(rdwr_arg)))
			return -EFAULT;

		if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
			return -EINVAL;

		/*
		 * Put an arbitrary limit on the number of messages that can
		 * be sent at once
		 */
		if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
			return -EINVAL;

		rdwr_pa = memdup_array_user(rdwr_arg.msgs,
					    rdwr_arg.nmsgs, sizeof(struct i2c_msg));
		if (IS_ERR(rdwr_pa))
			return PTR_ERR(rdwr_pa);

		res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
		kfree(rdwr_pa);
		return res;
	}

	case I2C_SMBUS: {
		struct i2c_smbus_ioctl_data data_arg;
		if (copy_from_user(&data_arg,
				   (struct i2c_smbus_ioctl_data __user *) arg,
				   sizeof(struct i2c_smbus_ioctl_data)))
			return -EFAULT;
		return i2cdev_ioctl_smbus(client, data_arg.read_write,
					  data_arg.command,
					  data_arg.size,
					  data_arg.data);
	}
	case I2C_RETRIES:
		if (arg > INT_MAX)
			return -EINVAL;

		client->adapter->retries = arg;
		break;
	case I2C_TIMEOUT:
		if (arg > INT_MAX)
			return -EINVAL;

		/* For historical reasons, user-space sets the timeout
		 * value in units of 10 ms.
		 */
		client->adapter->timeout = msecs_to_jiffies(arg * 10);
		break;
	default:
		/* NOTE:  returning a fault code here could cause trouble
		 * in buggy userspace code.  Some old kernel bugs returned
		 * zero in this case, and userspace code might accidentally
		 * have depended on that bug.
		 */
		return -ENOTTY;
	}
	return 0;
}

#ifdef CONFIG_COMPAT

struct i2c_smbus_ioctl_data32 {
	u8 read_write;
	u8 command;
	u32 size;
	compat_caddr_t data; /* union i2c_smbus_data *data */
};

struct i2c_msg32 {
	u16 addr;
	u16 flags;
	u16 len;
	compat_caddr_t buf;
};

struct i2c_rdwr_ioctl_data32 {
	compat_caddr_t msgs; /* struct i2c_msg __user *msgs */
	u32 nmsgs;
};

static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;
	switch (cmd) {
	case I2C_FUNCS:
		funcs = i2c_get_functionality(client->adapter);
		return put_user(funcs, (compat_ulong_t __user *)arg);
	case I2C_RDWR: {
		struct i2c_rdwr_ioctl_data32 rdwr_arg;
		struct i2c_msg32 __user *p;
		struct i2c_msg *rdwr_pa;
		int i, res;

		if (copy_from_user(&rdwr_arg,
				   (struct i2c_rdwr_ioctl_data32 __user *)arg,
				   sizeof(rdwr_arg)))
			return -EFAULT;

		if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
			return -EINVAL;

		if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
			return -EINVAL;

		rdwr_pa = kmalloc_array(rdwr_arg.nmsgs, sizeof(struct i2c_msg),
				      GFP_KERNEL);
		if (!rdwr_pa)
			return -ENOMEM;

		p = compat_ptr(rdwr_arg.msgs);
		for (i = 0; i < rdwr_arg.nmsgs; i++) {
			struct i2c_msg32 umsg;
			if (copy_from_user(&umsg, p + i, sizeof(umsg))) {
				kfree(rdwr_pa);
				return -EFAULT;
			}
			rdwr_pa[i] = (struct i2c_msg) {
				.addr = umsg.addr,
				.flags = umsg.flags,
				.len = umsg.len,
				.buf = (__force __u8 *)compat_ptr(umsg.buf),
			};
		}

		res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
		kfree(rdwr_pa);
		return res;
	}
	case I2C_SMBUS: {
		struct i2c_smbus_ioctl_data32	data32;
		if (copy_from_user(&data32,
				   (void __user *) arg,
				   sizeof(data32)))
			return -EFAULT;
		return i2cdev_ioctl_smbus(client, data32.read_write,
					  data32.command,
					  data32.size,
					  compat_ptr(data32.data));
	}
	default:
		return i2cdev_ioctl(file, cmd, arg);
	}
}
#else
#define compat_i2cdev_ioctl NULL
#endif

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;

	adap = i2c_get_adapter(minor);
	if (!adap)
		return -ENODEV;

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

	client->adapter = adap;
	file->private_data = client;

	return 0;
}

static int i2cdev_release(struct inode *inode, struct file *file)
{
	struct i2c_client *client = file->private_data;

	i2c_put_adapter(client->adapter);
	kfree(client);
	file->private_data = NULL;

	return 0;
}

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.compat_ioctl	= compat_i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

/* ------------------------------------------------------------------------- */

static const struct class i2c_dev_class = {
	.name = "i2c-dev",
	.dev_groups = i2c_groups,
};

static void i2cdev_dev_release(struct device *dev)
{
	struct i2c_dev *i2c_dev;

	i2c_dev = container_of(dev, struct i2c_dev, dev);
	kfree(i2c_dev);
}

static int i2cdev_attach_adapter(struct device *dev)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;

	if (dev->type != &i2c_adapter_type)
		return NOTIFY_DONE;
	adap = to_i2c_adapter(dev);

	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return NOTIFY_DONE;

	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
	i2c_dev->cdev.owner = THIS_MODULE;

	device_initialize(&i2c_dev->dev);
	i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
	i2c_dev->dev.class = &i2c_dev_class;
	i2c_dev->dev.parent = &adap->dev;
	i2c_dev->dev.release = i2cdev_dev_release;

	res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);
	if (res)
		goto err_put_i2c_dev;

	res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);
	if (res)
		goto err_put_i2c_dev;

	pr_debug("adapter [%s] registered as minor %d\n", adap->name, adap->nr);
	return NOTIFY_OK;

err_put_i2c_dev:
	put_i2c_dev(i2c_dev, false);
	return NOTIFY_DONE;
}

static int i2cdev_detach_adapter(struct device *dev)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;

	if (dev->type != &i2c_adapter_type)
		return NOTIFY_DONE;
	adap = to_i2c_adapter(dev);

	i2c_dev = i2c_dev_get_by_minor(adap->nr);
	if (!i2c_dev) /* attach_adapter must have failed */
		return NOTIFY_DONE;

	put_i2c_dev(i2c_dev, true);

	pr_debug("adapter [%s] unregistered\n", adap->name);
	return NOTIFY_OK;
}

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
			 void *data)
{
	struct device *dev = data;

	switch (action) {
	case BUS_NOTIFY_ADD_DEVICE:
		return i2cdev_attach_adapter(dev);
	case BUS_NOTIFY_DEL_DEVICE:
		return i2cdev_detach_adapter(dev);
	}

	return NOTIFY_DONE;
}

static struct notifier_block i2cdev_notifier = {
	.notifier_call = i2cdev_notifier_call,
};

/* ------------------------------------------------------------------------- */

static int __init i2c_dev_attach_adapter(struct device *dev, void *dummy)
{
	i2cdev_attach_adapter(dev);
	return 0;
}

static int __exit i2c_dev_detach_adapter(struct device *dev, void *dummy)
{
	i2cdev_detach_adapter(dev);
	return 0;
}

/*
 * module load/unload record keeping
 */

static int __init i2c_dev_init(void)
{
	int res;

	pr_info("i2c /dev entries driver\n");

	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;

	res = class_register(&i2c_dev_class);
	if (res)
		goto out_unreg_chrdev;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2c_dev_attach_adapter);

	return 0;

out_unreg_class:
	class_unregister(&i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	pr_err("Driver Initialisation failed\n");
	return res;
}

static void __exit i2c_dev_exit(void)
{
	bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);
	i2c_for_each_dev(NULL, i2c_dev_detach_adapter);
	class_unregister(&i2c_dev_class);
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
}

MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_AUTHOR("Simon G. Vogl <simon@tk.uni-linz.ac.at>");
MODULE_DESCRIPTION("I2C /dev entries driver");
MODULE_LICENSE("GPL");

module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
]

32/4 2026/01/29 19:32 llm patch-generator
1m Model:
gemini-3-pro-preview

Tokens:
							input: 32616
							output: 27
							thoughts: 5689
						

Thoughts:
**Analysis of an I2C SMBus Driver Vulnerability and Proposed Fix**

Alright, let's break this down. My primary concern here is a potential out-of-bounds read/write vulnerability in the `hid-ft260` driver, specifically within the `i2cdev_ioctl_smbus` function when handling I2C SMBus transactions. The core problem revolves around insufficient validation of user-provided data, particularly the length parameter (`data->block[0]`) in `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` commands.

I've traced the execution flow. When a user calls these commands, a `union i2c_smbus_data temp = {}` is initialized on the stack. Now, if we have `I2C_SMBUS_I2C_BLOCK_DATA` WRITE, the length byte comes directly from user space. The critical call `memcpy(&rep->data[1], data, data_len);` in `ft260_smbus_write` copies user data (pointed to by `data`) into a fixed-size buffer (`rep->data`). If `data_len` (derived from `data->block[0]`) is larger than the allocated size, BAM! out-of-bounds read and possible crash.

The KASAN report, which I trust implicitly, confirms a "Read of size 42" and mentions `ft260_smbus_write+0x156`. I've confirmed that this is where the vulnerability lies: data is read from `data->block+1`.

The vulnerability exists in the `I2C_SMBUS_I2C_BLOCK_DATA` WRITE case and the `I2C_SMBUS_BLOCK_DATA` cases if write is performed.

The fix I'm proposing is straightforward. I need to add a check, within the `ft260_smbus_xfer` function, at the start of each of the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases, to validate the length byte (`data->block[0]`) against a maximum allowed value, which I'm taking to be `I2C_SMBUS_BLOCK_MAX` (which is 32). This check will prevent user-supplied lengths that would cause an out-of-bounds access. An `-EINVAL` error will be returned if the check fails.

Now, a couple of caveats. I've noted that for `I2C_SMBUS_BLOCK_DATA` READ from `i2c-dev`, `data->block[0]` is initialized to 0. The check I'm adding will be technically redundant because 0 <= 32. This is safe but is less clear from a code-readability perspective, so I may need to make a note about that during code review. However, if the code in question is called by kernel space (e.g., from `i2c_smbus_read_block_data`), the check will be essential to prevent issues.

The `i2c_smbus_read_block_data` function is passing an uninitialized `data` structure when the call is coming from the kernel, which means `data.block[0]` is garbage. This means there's a latent bug where the length used for the read can be random, with unpredictable behavior. Adding the check is even more important to prevent an exploitable vulnerability. Even if we return -EINVAL, the system is safer than previously.

So, in summary: the fix is a simple, yet crucial, bounds check on the user-provided length byte, placed right at the start of the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` case statements in `ft260_smbus_xfer`. This will stop malicious actors from leveraging a critical OOB vulnerability.



33/4 2026/01/29 19:33 tool read-file
0m Args:
map[File:drivers/hid/hid-ft260.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
 * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
 *
 * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
 *
 * Data Sheet:
 *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
 */

#include "hid-ids.h"
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/usb.h>

#ifdef DEBUG
static int ft260_debug = 1;
#else
static int ft260_debug;
#endif
module_param_named(debug, ft260_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");

#define ft260_dbg(format, arg...)					  \
	do {								  \
		if (ft260_debug)					  \
			pr_info("%s: " format, __func__, ##arg);	  \
	} while (0)

#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)

#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */

/*
 * The ft260 input report format defines 62 bytes for the data payload, but
 * when requested 62 bytes, the controller returns 60 and 2 in separate input
 * reports. To achieve better performance with the multi-report read data
 * transfers, we set the maximum read payload length to a multiple of 60.
 * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
 * which is excessive; On the other hand, some higher layer drivers like at24
 * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
 * of I2C for potentially troublesome amounts of time, we select the maximum
 * read payload length to be 180 bytes.
*/
#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)

/*
 * Device interface configuration.
 * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
 * First implementes USB HID to I2C bridge function and
 * second - USB HID to UART bridge function.
 */
enum {
	FT260_MODE_ALL			= 0x00,
	FT260_MODE_I2C			= 0x01,
	FT260_MODE_UART			= 0x02,
	FT260_MODE_BOTH			= 0x03,
};

/* Control pipe */
enum {
	FT260_GET_RQST_TYPE		= 0xA1,
	FT260_GET_REPORT		= 0x01,
	FT260_SET_RQST_TYPE		= 0x21,
	FT260_SET_REPORT		= 0x09,
	FT260_FEATURE			= 0x03,
};

/* Report IDs / Feature In */
enum {
	FT260_CHIP_VERSION		= 0xA0,
	FT260_SYSTEM_SETTINGS		= 0xA1,
	FT260_I2C_STATUS		= 0xC0,
	FT260_I2C_READ_REQ		= 0xC2,
	FT260_I2C_REPORT_MIN		= 0xD0,
	FT260_I2C_REPORT_MAX		= 0xDE,
	FT260_GPIO			= 0xB0,
	FT260_UART_INTERRUPT_STATUS	= 0xB1,
	FT260_UART_STATUS		= 0xE0,
	FT260_UART_RI_DCD_STATUS	= 0xE1,
	FT260_UART_REPORT		= 0xF0,
};

/* Feature Out */
enum {
	FT260_SET_CLOCK			= 0x01,
	FT260_SET_I2C_MODE		= 0x02,
	FT260_SET_UART_MODE		= 0x03,
	FT260_ENABLE_INTERRUPT		= 0x05,
	FT260_SELECT_GPIO2_FUNC		= 0x06,
	FT260_ENABLE_UART_DCD_RI	= 0x07,
	FT260_SELECT_GPIOA_FUNC		= 0x08,
	FT260_SELECT_GPIOG_FUNC		= 0x09,
	FT260_SET_INTERRUPT_TRIGGER	= 0x0A,
	FT260_SET_SUSPEND_OUT_POLAR	= 0x0B,
	FT260_ENABLE_UART_RI_WAKEUP	= 0x0C,
	FT260_SET_UART_RI_WAKEUP_CFG	= 0x0D,
	FT260_SET_I2C_RESET		= 0x20,
	FT260_SET_I2C_CLOCK_SPEED	= 0x22,
	FT260_SET_UART_RESET		= 0x40,
	FT260_SET_UART_CONFIG		= 0x41,
	FT260_SET_UART_BAUD_RATE	= 0x42,
	FT260_SET_UART_DATA_BIT		= 0x43,
	FT260_SET_UART_PARITY		= 0x44,
	FT260_SET_UART_STOP_BIT		= 0x45,
	FT260_SET_UART_BREAKING		= 0x46,
	FT260_SET_UART_XON_XOFF		= 0x49,
};

/* Response codes in I2C status report */
enum {
	FT260_I2C_STATUS_SUCCESS	= 0x00,
	FT260_I2C_STATUS_CTRL_BUSY	= 0x01,
	FT260_I2C_STATUS_ERROR		= 0x02,
	FT260_I2C_STATUS_ADDR_NO_ACK	= 0x04,
	FT260_I2C_STATUS_DATA_NO_ACK	= 0x08,
	FT260_I2C_STATUS_ARBITR_LOST	= 0x10,
	FT260_I2C_STATUS_CTRL_IDLE	= 0x20,
	FT260_I2C_STATUS_BUS_BUSY	= 0x40,
};

/* I2C Conditions flags */
enum {
	FT260_FLAG_NONE			= 0x00,
	FT260_FLAG_START		= 0x02,
	FT260_FLAG_START_REPEATED	= 0x03,
	FT260_FLAG_STOP			= 0x04,
	FT260_FLAG_START_STOP		= 0x06,
	FT260_FLAG_START_STOP_REPEATED	= 0x07,
};

#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)

/* Feature In reports */

struct ft260_get_chip_version_report {
	u8 report;		/* FT260_CHIP_VERSION */
	u8 chip_code[4];	/* FTDI chip identification code */
	u8 reserved[8];
} __packed;

struct ft260_get_system_status_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 chip_mode;		/* DCNF0 and DCNF1 status, bits 0-1 */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
	u8 suspend_status;	/* 0 - not suspended, 1 - suspended */
	u8 pwren_status;	/* 0 - FT260 is not ready, 1 - ready */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
	u8 hid_over_i2c_en;	/* 0 - disabled, 1 - enabled */
	u8 gpio2_function;	/* 0 - GPIO,  1 - SUSPOUT, */
				/* 2 - PWREN, 4 - TX_LED */
	u8 gpioA_function;	/* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
	u8 gpioG_function;	/* 0 - GPIO, 2 - PWREN, */
				/* 5 - RX_LED, 6 - BCD_DET */
	u8 suspend_out_pol;	/* 0 - active-high, 1 - active-low */
	u8 enable_wakeup_int;	/* 0 - disabled, 1 - enabled */
	u8 intr_cond;		/* Interrupt trigger conditions */
	u8 power_saving_en;	/* 0 - disabled, 1 - enabled */
	u8 reserved[10];
} __packed;

struct ft260_get_i2c_status_report {
	u8 report;		/* FT260_I2C_STATUS */
	u8 bus_status;		/* I2C bus status */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
	u8 reserved;
} __packed;

/* Feature Out reports */

struct ft260_set_system_clock_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_CLOCK */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
} __packed;

struct ft260_set_i2c_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_MODE */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
} __packed;

struct ft260_set_uart_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_UART_MODE */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

struct ft260_set_i2c_reset_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_RESET */
} __packed;

struct ft260_set_i2c_speed_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_CLOCK_SPEED */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
} __packed;

/* Data transfer reports */

struct ft260_i2c_write_request_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	u8 length;		/* data payload length */
	u8 data[FT260_WR_DATA_MAX]; /* data payload */
} __packed;

struct ft260_i2c_read_request_report {
	u8 report;		/* FT260_I2C_READ_REQ */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	__le16 length;		/* data payload length */
} __packed;

struct ft260_i2c_input_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 length;		/* data payload length */
	u8 data[2];		/* data payload */
} __packed;

static const struct hid_device_id ft260_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
			 USB_DEVICE_ID_FT260) },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(hid, ft260_devices);

struct ft260_device {
	struct i2c_adapter adap;
	struct hid_device *hdev;
	struct completion wait;
	struct mutex lock;
	u8 write_buf[FT260_REPORT_MAX_LENGTH];
	unsigned long need_wakeup_at;
	u8 *read_buf;
	u16 read_idx;
	u16 read_len;
	u16 clock;
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
					unsigned char report_id, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmalloc(len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
				 HID_REQ_GET_REPORT);
	if (likely(ret == len))
		memcpy(data, buf, len);
	else if (ret >= 0)
		ret = -EIO;
	kfree(buf);
	return ret;
}

static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	buf[0] = FT260_SYSTEM_SETTINGS;

	ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
				 HID_REQ_SET_REPORT);

	kfree(buf);
	return ret;
}

static int ft260_i2c_reset(struct hid_device *hdev)
{
	struct ft260_set_i2c_reset_report report;
	int ret;

	report.request = FT260_SET_I2C_RESET;

	ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
	if (ret < 0) {
		hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
		return ret;
	}

	ft260_dbg("done\n");
	return ret;
}

static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
	struct hid_device *hdev = dev->hdev;
	struct ft260_get_i2c_status_report report;
	int ret;

	if (time_is_before_jiffies(dev->need_wakeup_at)) {
		ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
						(u8 *)&report, sizeof(report));
		if (unlikely(ret < 0)) {
			hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
				ret);
		} else {
			dev->need_wakeup_at = jiffies +
				msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
			ft260_dbg("bus_status %#02x, wakeup\n",
				  report.bus_status);
		}
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
					   (u8 *)&report, sizeof(report));
	if (unlikely(ret < 0)) {
		hid_err(hdev, "failed to retrieve status: %d\n", ret);
		return ret;
	}

	dev->clock = le16_to_cpu(report.clock);
	ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
		  dev->clock);

	if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
		return -EAGAIN;

	/*
	 * The error condition (bit 1) is a status bit reflecting any
	 * error conditions. When any of the bits 2, 3, or 4 are raised
	 * to 1, bit 1 is also set to 1.
	 */
	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
		return -EIO;
	}

	return 0;
}

static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
				   size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_output_report(hdev, buf, len);

	kfree(buf);
	return ret;
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
						u8 *data, int len)
{
	u8 bus_busy;
	int ret, usec, try = 100;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)data;

	ret = ft260_hid_output_report(hdev, data, len);
	if (ret < 0) {
		hid_err(hdev, "%s: failed to start transfer, ret %d\n",
			__func__, ret);
		ft260_i2c_reset(hdev);
		return ret;
	}

	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
	usec = len * 9000 / dev->clock;
	if (usec > 2000) {
		usec -= 1500;
		usleep_range(usec, usec + 100);
		ft260_dbg("wait %d usec, len %d\n", usec, len);
	}

	/*
	 * Do not check the busy bit for combined transactions
	 * since the controller keeps the bus busy between writing
	 * and reading IOs to ensure an atomic operation.
	 */
	if (rep->flag == FT260_FLAG_START)
		bus_busy = 0;
	else
		bus_busy = FT260_I2C_STATUS_BUS_BUSY;

	do {
		ret = ft260_xfer_status(dev, bus_busy);
		if (ret != -EAGAIN)
			break;
	} while (--try);

	if (ret == 0)
		return 0;

	ft260_i2c_reset(hdev);
	return -EIO;
}

static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
			   int len, u8 flag)
{
	int ret, wr_len, idx = 0;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (len < 1)
		return -EINVAL;

	rep->flag = FT260_FLAG_START;

	do {
		if (len <= FT260_WR_DATA_MAX) {
			wr_len = len;
			if (flag == FT260_FLAG_START_STOP)
				rep->flag |= FT260_FLAG_STOP;
		} else {
			wr_len = FT260_WR_DATA_MAX;
		}

		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
		rep->address = addr;
		rep->length = wr_len;

		memcpy(rep->data, &data[idx], wr_len);

		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
			  rep->report, addr, idx, len, wr_len,
			  rep->flag, data[0]);

		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
							   wr_len + 4);
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			return ret;
		}

		len -= wr_len;
		idx += wr_len;
		rep->flag = 0;

	} while (len > 0);

	return 0;
}

static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}

static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}

/*
 * A random read operation is implemented as a dummy write operation, followed
 * by a current address read operation. The dummy write operation is used to
 * load the target byte address into the current byte address counter, from
 * which the subsequent current address read operation then reads.
 */
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
	int ret;
	int wr_len = msgs[0].len;
	int rd_len = msgs[1].len;
	struct hid_device *hdev = dev->hdev;
	u8 addr = msgs[0].addr;
	u16 read_off = 0;

	if (wr_len > 2) {
		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
		return -EOPNOTSUPP;
	}

	if (ft260_debug) {
		if (wr_len == 2)
			read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
		else
			read_off = *msgs[0].buf;

		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
			read_off, rd_len, wr_len);
	}

	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
			      FT260_FLAG_START);
	if (ret < 0)
		return ret;

	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
			     FT260_FLAG_START_STOP_REPEATED);
	if (ret < 0)
		return ret;

	return 0;
}

static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
			  int num)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	if (num == 1) {
		if (msgs->flags & I2C_M_RD)
			ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
					     msgs->len, FT260_FLAG_START_STOP);
		else
			ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
					      msgs->len, FT260_FLAG_START_STOP);
		if (ret < 0)
			goto i2c_exit;

	} else {
		/* Combined write then read message */
		ret = ft260_i2c_write_read(dev, msgs);
		if (ret < 0)
			goto i2c_exit;
	}

	ret = num;
i2c_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	ft260_dbg("smbus size %d\n", size);

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "power management error: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	switch (size) {
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
		break;
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd,
						(u8 *)&data->word, 2,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
	default:
		hid_err(hdev, "unsupported smbus transaction size %d\n", size);
		ret = -EOPNOTSUPP;
	}

smbus_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static u32 ft260_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

static const struct i2c_adapter_quirks ft260_i2c_quirks = {
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
	.max_comb_1st_msg_len = 2,
};

static const struct i2c_algorithm ft260_i2c_algo = {
	.master_xfer = ft260_i2c_xfer,
	.smbus_xfer = ft260_smbus_xfer,
	.functionality = ft260_functionality,
};

static int ft260_get_system_config(struct hid_device *hdev,
				   struct ft260_get_system_status_report *cfg)
{
	int ret;
	int len = sizeof(struct ft260_get_system_status_report);

	ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
					   (u8 *)cfg, len);
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve system status\n");
		return ret;
	}
	return 0;
}

static int ft260_is_interface_enabled(struct hid_device *hdev)
{
	struct ft260_get_system_status_report cfg;
	struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
	int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
	int ret;

	ret = ft260_get_system_config(hdev, &cfg);
	if (ret < 0)
		return ret;

	ft260_dbg("interface:  0x%02x\n", interface);
	ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
	ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
	ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
	ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);

	switch (cfg.chip_mode) {
	case FT260_MODE_ALL:
	case FT260_MODE_BOTH:
		if (interface == 1)
			hid_info(hdev, "uart interface is not supported\n");
		else
			ret = 1;
		break;
	case FT260_MODE_UART:
		hid_info(hdev, "uart interface is not supported\n");
		break;
	case FT260_MODE_I2C:
		ret = 1;
		break;
	}
	return ret;
}

static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   u8 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", *field);
}

static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   __le16 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

#define FT260_ATTR_SHOW(name, reptype, id, type, func)			       \
	static ssize_t name##_show(struct device *kdev,			       \
				   struct device_attribute *attr, char *buf)   \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type *field = &rep.name;				       \
		int len = sizeof(rep);					       \
									       \
		return func(hdev, id, (u8 *)&rep, len, field, buf);	       \
	}

#define FT260_SSTAT_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_system_status_report,	       \
				FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)

#define FT260_I2CST_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,	       \
				FT260_I2C_STATUS, __le16, ft260_word_show)

#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func)	       \
	static ssize_t name##_store(struct device *kdev,		       \
				    struct device_attribute *attr,	       \
				    const char *buf, size_t count)	       \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type name;						       \
		int ret;						       \
									       \
		if (!func(buf, 10, (ctype *)&name)) {			       \
			rep.name = name;				       \
			rep.report = id;				       \
			rep.request = req;				       \
			ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
							   sizeof(rep));       \
			if (!ret)					       \
				ret = count;				       \
		} else {						       \
			ret = -EINVAL;					       \
		}							       \
		return ret;						       \
	}

#define FT260_BYTE_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 u8, u8, kstrtou8)

#define FT260_WORD_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 __le16, u16, kstrtou16)

FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);

FT260_SSTAT_ATTR_SHOW(pwren_status);
static DEVICE_ATTR_RO(pwren_status);

FT260_SSTAT_ATTR_SHOW(suspend_status);
static DEVICE_ATTR_RO(suspend_status);

FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
static DEVICE_ATTR_RO(hid_over_i2c_en);

FT260_SSTAT_ATTR_SHOW(power_saving_en);
static DEVICE_ATTR_RO(power_saving_en);

FT260_SSTAT_ATTR_SHOW(i2c_enable);
FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
		      FT260_SET_I2C_MODE);
static DEVICE_ATTR_RW(i2c_enable);

FT260_SSTAT_ATTR_SHOW(uart_mode);
FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
		      FT260_SET_UART_MODE);
static DEVICE_ATTR_RW(uart_mode);

FT260_SSTAT_ATTR_SHOW(clock_ctl);
FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
		      FT260_SET_CLOCK);
static DEVICE_ATTR_RW(clock_ctl);

FT260_I2CST_ATTR_SHOW(clock);
FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
		      FT260_SET_I2C_CLOCK_SPEED);
static DEVICE_ATTR_RW(clock);

static ssize_t i2c_reset_store(struct device *kdev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	struct hid_device *hdev = to_hid_device(kdev);
	int ret = ft260_i2c_reset(hdev);

	if (ret)
		return ret;
	return count;
}
static DEVICE_ATTR_WO(i2c_reset);

static const struct attribute_group ft260_attr_group = {
	.attrs = (struct attribute *[]) {
		  &dev_attr_chip_mode.attr,
		  &dev_attr_pwren_status.attr,
		  &dev_attr_suspend_status.attr,
		  &dev_attr_hid_over_i2c_en.attr,
		  &dev_attr_power_saving_en.attr,
		  &dev_attr_i2c_enable.attr,
		  &dev_attr_uart_mode.attr,
		  &dev_attr_clock_ctl.attr,
		  &dev_attr_i2c_reset.attr,
		  &dev_attr_clock.attr,
		  NULL
	}
};

static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	struct ft260_device *dev;
	struct ft260_get_chip_version_report version;
	int ret;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	ret = hid_parse(hdev);
	if (ret) {
		hid_err(hdev, "failed to parse HID\n");
		return ret;
	}

	ret = hid_hw_start(hdev, 0);
	if (ret) {
		hid_err(hdev, "failed to start HID HW\n");
		return ret;
	}

	ret = hid_hw_open(hdev);
	if (ret) {
		hid_err(hdev, "failed to open HID HW\n");
		goto err_hid_stop;
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
					   (u8 *)&version, sizeof(version));
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve chip version\n");
		goto err_hid_close;
	}

	hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
		 version.chip_code[0], version.chip_code[1],
		 version.chip_code[2], version.chip_code[3]);

	ret = ft260_is_interface_enabled(hdev);
	if (ret <= 0)
		goto err_hid_close;

	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
		hdev->version >> 8, hdev->version & 0xff, hdev->name,
		hdev->phys);

	hid_set_drvdata(hdev, dev);
	dev->hdev = hdev;
	dev->adap.owner = THIS_MODULE;
	dev->adap.class = I2C_CLASS_HWMON;
	dev->adap.algo = &ft260_i2c_algo;
	dev->adap.quirks = &ft260_i2c_quirks;
	dev->adap.dev.parent = &hdev->dev;
	snprintf(dev->adap.name, sizeof(dev->adap.name),
		 "FT260 usb-i2c bridge");

	mutex_init(&dev->lock);
	init_completion(&dev->wait);

	ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
	if (ret)
		ft260_i2c_reset(hdev);

	i2c_set_adapdata(&dev->adap, dev);
	ret = i2c_add_adapter(&dev->adap);
	if (ret) {
		hid_err(hdev, "failed to add i2c adapter\n");
		goto err_hid_close;
	}

	ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
	if (ret < 0) {
		hid_err(hdev, "failed to create sysfs attrs\n");
		goto err_i2c_free;
	}

	return 0;

err_i2c_free:
	i2c_del_adapter(&dev->adap);
err_hid_close:
	hid_hw_close(hdev);
err_hid_stop:
	hid_hw_stop(hdev);
	return ret;
}

static void ft260_remove(struct hid_device *hdev)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);

	if (!dev)
		return;

	sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
	i2c_del_adapter(&dev->adap);

	hid_hw_close(hdev);
	hid_hw_stop(hdev);
}

static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
			   u8 *data, int size)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);
	struct ft260_i2c_input_report *xfer = (void *)data;

	if (xfer->report >= FT260_I2C_REPORT_MIN &&
	    xfer->report <= FT260_I2C_REPORT_MAX) {
		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
			  xfer->length);

		if ((dev->read_buf == NULL) ||
		    (xfer->length > dev->read_len - dev->read_idx)) {
			hid_err(hdev, "unexpected report %#02x, length %d\n",
				xfer->report, xfer->length);
			return -1;
		}

		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
		       xfer->length);
		dev->read_idx += xfer->length;

		if (dev->read_idx == dev->read_len)
			complete(&dev->wait);

	} else {
		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
	}
	return 0;
}

static struct hid_driver ft260_driver = {
	.name		= "ft260",
	.id_table	= ft260_devices,
	.probe		= ft260_probe,
	.remove		= ft260_remove,
	.raw_event	= ft260_raw_event,
};

module_hid_driver(ft260_driver);
MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
MODULE_LICENSE("GPL v2");
]

34/4 2026/01/29 19:33 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 52054
							output: 913
							thoughts: 1463
						

Thoughts:
**Analyzing and Applying a Code Fix**

Okay, so I'm looking at this fix, and it seems pretty straightforward. The task is to modify the `drivers/hid/hid-ft260.c` file. The code deals with I2C SMBus block data handling, specifically within the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases.

It looks like the change involves adding the `if (data->block[0] > I2C_SMBUS_BLOCK_MAX)` condition within each block to validate the block size. I'll make the change directly.  Since the blocks are adjacent, I can do a single block replacement to keep things clean. I need to make sure I get the indentation and all the existing code perfectly matched to avoid any unintended side effects. Using `codeeditor` to make the modification gives me confidence that it will be applied correctly.  This seems like a solid, isolated fix; I'm confident it addresses the underlying issue.



35/4 2026/01/29 19:34 tool codeeditor
0m Args:
map[CurrentCode:	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break; NewCode:	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break; SourceFile:drivers/hid/hid-ft260.c]

Results:
map[]

36/4 2026/01/29 19:34 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 54662
							output: 21
							thoughts: 316
						

Thoughts:
**Fix Verification and Contextual Analysis**

Okay, so I've implemented the crucial fix. I added a sanity check – `if (data->block[0] > I2C_SMBUS_BLOCK_MAX)` – to the `ft260_smbus_xfer` function, specifically within both the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases. This should effectively prevent that nasty out-of-bounds read/write to the `data->block` buffer that was triggering the KASAN stack-out-of-bounds crash.

Now, I need to make absolutely sure `I2C_SMBUS_BLOCK_MAX` is accessible within the `hid-ft260.c` context. Since the module includes `<linux/i2c.h`, I expect it to be defined. Let me just double-check the includes in `drivers/hid/hid-ft260.c` to be certain. Yes, confirming `<linux/i2c.h>` is included along with the other necessary headers.

Just to be completely thorough, let me quickly verify that `I2C_SMBUS_BLOCK_MAX` is indeed defined. We know it's defined in either `uapi/linux/i2c.h` or `linux/i2c.h`. Good, good. Should be all set.



37/4 2026/01/29 19:34 tool codesearch-dir-index
0m Args:
map[Dir:include/linux]

Results:
map[Files:[8250_pci.h acct.h acpi.h acpi_amd_wbrf.h acpi_dma.h acpi_iort.h acpi_mdio.h acpi_pmtmr.h acpi_rimt.h acpi_viot.h adb.h adfs_fs.h adi-axi-common.h adreno-smmu-priv.h adxl.h aer.h agp_backend.h agpgart.h ahci-remap.h ahci_platform.h aio.h alarmtimer.h alcor_pci.h align.h alloc_tag.h altera_jtaguart.h altera_uart.h amd-iommu.h amd-pmf-io.h annotate.h anon_inodes.h aperture.h apm-emulation.h apm_bios.h apple-gmux.h arch_topology.h args.h arm-cci.h arm-smccc.h arm_ffa.h arm_mpam.h arm_sdei.h armada-37xx-rwtm-mailbox.h array_size.h ascii85.h asn1.h asn1_ber_bytecode.h asn1_decoder.h asn1_encoder.h assoc_array.h assoc_array_priv.h async.h async_tx.h ata.h ata_platform.h atalk.h atm.h atm_tcp.h atmdev.h atmel-isc-media.h atmel-ssc.h atmel_pdc.h atomic.h attribute_container.h audit.h audit_arch.h auto_dev-ioctl.h auto_fs.h auxiliary_bus.h auxvec.h average.h backing-dev-defs.h backing-dev.h backing-file.h backlight.h badblocks.h balloon_compaction.h base64.h bcd.h bch.h bcm47xx_nvram.h bcm47xx_sprom.h bcm47xx_wdt.h bcm963xx_nvram.h bcm963xx_tag.h binfmts.h bio-integrity.h bio.h bit_spinlock.h bitfield.h bitmap-str.h bitmap.h bitops.h bitrev.h bits.h blk-cgroup.h blk-crypto-profile.h blk-crypto.h blk-integrity.h blk-mq-dma.h blk-mq.h blk-pm.h blk_types.h blkdev.h blkpg.h blktrace_api.h blockgroup_lock.h bma150.h bootconfig.h bootmem_info.h bottom_half.h bpf-cgroup-defs.h bpf-cgroup.h bpf-netns.h bpf.h bpf_crypto.h bpf_lirc.h bpf_local_storage.h bpf_lsm.h bpf_mem_alloc.h bpf_mprog.h bpf_trace.h bpf_types.h bpf_verifier.h bpfptr.h brcmphy.h bsearch.h bsg-lib.h bsg.h btf.h btf_ids.h btree-128.h btree-type.h btree.h btrfs.h buffer_head.h bug.h build-salt.h build_bug.h buildid.h bvec.h c2port.h cache.h cache_coherency.h cacheflush.h cacheinfo.h call_once.h capability.h cb710.h cc_platform.h cciss_ioctl.h ccp.h cdev.h cdrom.h cfag12864b.h cfi.h cfi_types.h cgroup-defs.h cgroup.h cgroup_api.h cgroup_dmem.h cgroup_namespace.h cgroup_rdma.h cgroup_refcnt.h cgroup_subsys.h circ_buf.h cleanup.h clk-provider.h clk.h clkdev.h clockchips.h clocksource.h clocksource_ids.h closure.h cma.h cmpxchg-emu.h cn_proc.h cnt32_to_63.h coda.h codetag.h compaction.h compat.h compiler-clang.h compiler-gcc.h compiler-version.h compiler.h compiler_attributes.h compiler_types.h completion.h component.h configfs.h connector.h console.h console_struct.h consolemap.h const.h container.h container_of.h context_tracking.h context_tracking_irq.h context_tracking_state.h cookie.h cordic.h coredump.h coresight-pmu.h coresight-stm.h coresight.h count_zeros.h counter.h cper.h cpu.h cpu_cooling.h cpu_pm.h cpu_rmap.h cpu_smt.h cpufeature.h cpufreq.h cpuhotplug.h cpuhplock.h cpuidle.h cpuidle_haltpoll.h cpumask.h cpumask_api.h cpumask_types.h cpuset.h crash_core.h crash_dump.h crash_reserve.h crc-ccitt.h crc-itu-t.h crc-t10dif.h crc16.h crc32.h crc32c.h crc32poly.h crc4.h crc64.h crc7.h crc8.h cred.h crypto.h cs5535.h ctype.h cuda.h damon.h dasd_mod.h davinci_emac.h dax.h dca.h dcache.h dccp.h debug_locks.h debugfs.h debugobjects.h delay.h delayacct.h delayed_call.h dev_printk.h devcoredump.h devfreq-event.h devfreq-governor.h devfreq.h devfreq_cooling.h device-mapper.h device.h device_cgroup.h devm-helpers.h devpts_fs.h dfl.h dibs.h digsig.h dim.h dio.h dirent.h dlm.h dlm_plock.h dm-bufio.h dm-dirty-log.h dm-io.h dm-kcopyd.h dm-region-hash.h dm-verity-loadpin.h dm9000.h dma-buf-mapping.h dma-buf.h dma-direct.h dma-direction.h dma-fence-array.h dma-fence-chain.h dma-fence-unwrap.h dma-fence.h dma-heap.h dma-map-ops.h dma-mapping.h dma-resv.h dmaengine.h dmapool.h dmar.h dmi.h dnotify.h dns_resolver.h dpll.h dqblk_qtree.h dqblk_v1.h dqblk_v2.h drbd.h drbd_config.h drbd_genl.h drbd_genl_api.h drbd_limits.h ds2782_battery.h dtlk.h dtpm.h dw_apb_timer.h dynamic_debug.h dynamic_queue_limits.h earlycpio.h ecryptfs.h edac.h edd.h eeprom_93cx6.h efi-bgrt.h efi.h efi_embedded_fw.h efs_vh.h ehl_pse_io_aux.h eisa.h elf-fdpic.h elf-randomize.h elf.h elfcore-compat.h elfcore.h elfnote-lto.h elfnote.h enclosure.h energy_model.h entry-common.h entry-virt.h err.h errname.h errno.h error-injection.h errqueue.h errseq.h etherdevice.h ethtool.h ethtool_netlink.h eventfd.h eventpoll.h evm.h execmem.h export-internal.h export.h exportfs.h ext2_fs.h extable.h extcon-provider.h extcon.h f2fs_fs.h f75375s.h falloc.h fanotify.h fault-inject-usercopy.h fault-inject.h fb.h fbcon.h fcdevice.h fcntl.h fd.h fddidevice.h fdtable.h fec.h fiemap.h file.h file_ref.h fileattr.h filelock.h filter.h find.h fips.h firewire.h firmware-map.h firmware.h fixp-arith.h flat.h flex_proportions.h folio_queue.h font.h fortify-string.h fprobe.h fpu.h freezer.h fs.h fs_api.h fs_context.h fs_dirent.h fs_parser.h fs_pin.h fs_stack.h fs_struct.h fscache-cache.h fscache.h fscrypt.h fsi-occ.h fsi-sbefifo.h fsi.h fsl-diu-fb.h fsl_devices.h fsl_hypervisor.h fsl_ifc.h fsldma.h fsnotify.h fsnotify_backend.h fsverity.h ftrace.h ftrace_irq.h ftrace_regs.h futex.h fw_table.h fwctl.h fwnode.h fwnode_mdio.h gameport.h gcd.h genalloc.h generic-radix-tree.h genl_magic_func.h genl_magic_struct.h getcpu.h gfp.h gfp_api.h gfp_types.h glob.h gnss.h goldfish.h gpio-pxa.h gpio.h gpio_keys.h greybus.h group_cpus.h hardirq.h hash.h hashtable.h hashtable_api.h hdlc.h hdlcdrv.h hdmi.h hex.h hfs_common.h hid-debug.h hid-over-i2c.h hid-over-spi.h hid-roccat.h hid-sensor-hub.h hid-sensor-ids.h hid.h hid_bpf.h hidden.h hiddev.h hidraw.h highmem-internal.h highmem.h highuid.h hil.h hil_mlc.h hippidevice.h hisi_acc_qm.h hmm-dma.h hmm.h host1x.h host1x_context_bus.h hp_sdc.h hpet.h hrtimer.h hrtimer_api.h hrtimer_defs.h hrtimer_types.h hte.h huge_mm.h hugetlb.h hugetlb_cgroup.h hugetlb_inline.h hung_task.h hw_bitfield.h hw_breakpoint.h hw_random.h hwmon-sysfs.h hwmon-vid.h hwmon.h hwspinlock.h hyperv.h hypervisor.h i2c-algo-bit.h i2c-algo-pca.h i2c-algo-pcf.h i2c-atr.h i2c-dev.h i2c-mux.h i2c-of-prober.h i2c-smbus.h i2c.h i8042.h i8253.h i8254.h icmp.h icmpv6.h idle_inject.h idr.h ieee80211-eht.h ieee80211-he.h ieee80211-ht.h ieee80211-mesh.h ieee80211-nan.h ieee80211-p2p.h ieee80211-s1g.h ieee80211-vht.h ieee80211.h ieee802154.h if_arp.h if_bridge.h if_eql.h if_ether.h if_fddi.h if_hsr.h if_link.h if_macvlan.h if_phonet.h if_pppol2tp.h if_pppox.h if_rmnet.h if_tap.h if_team.h if_tun.h if_tunnel.h if_vlan.h igmp.h ihex.h ima.h imx-media.h in.h in6.h indirect_call_wrapper.h inet.h inet_diag.h inetdevice.h init.h init_ohci1394_dma.h init_syscalls.h init_task.h initrd.h inotify.h input.h instruction_pointer.h instrumentation.h instrumented.h int_log.h integrity.h intel-ish-client-if.h intel_dg_nvm_aux.h intel_pmt_features.h intel_rapl.h intel_tcc.h intel_th.h intel_tpmi.h intel_vsec.h interconnect-clk.h interconnect-provider.h interconnect.h interrupt.h interval_tree.h interval_tree_generic.h io-64-nonatomic-hi-lo.h io-64-nonatomic-lo-hi.h io-mapping.h io-pgtable.h io.h io_uring.h io_uring_types.h ioam6.h ioam6_genl.h ioam6_iptunnel.h iocontext.h iomap.h iommu-dma.h iommu-helper.h iommu.h iommufd.h iopoll.h ioport.h ioprio.h ioremap.h iosys-map.h iov_iter.h iova.h iova_bitmap.h ip.h ipack.h ipc.h ipc_namespace.h ipmi.h ipmi_smi.h ipv6.h ipv6_route.h irq-entry-common.h irq.h irq_poll.h irq_sim.h irq_work.h irq_work_types.h irqbypass.h irqchip.h irqdesc.h irqdomain.h irqdomain_defs.h irqflags.h irqflags_types.h irqhandler.h irqnr.h irqreturn.h isa-dma.h isa.h isapnp.h iscsi_boot_sysfs.h iscsi_ibft.h ism.h iversion.h jbd2.h jhash.h jiffies.h journal-head.h joystick.h jump_label.h jump_label_ratelimit.h jz4740-adc.h jz4780-nemc.h kallsyms.h kasan-checks.h kasan-enabled.h kasan-tags.h kasan.h kbd_diacr.h kbd_kern.h kbuild.h kconfig.h kcore.h kcov.h kcsan-checks.h kcsan.h kdb.h kdebug.h kdev_t.h kern_levels.h kernel-page-flags.h kernel.h kernel_read_file.h kernel_stat.h kernelcapi.h kernfs.h kexec.h kexec_handover.h key-type.h key.h keyboard.h keyctl.h kfence.h kfifo.h kgdb.h khugepaged.h klist.h kmemleak.h kmod.h kmsan-checks.h kmsan.h kmsan_string.h kmsan_types.h kmsg_dump.h kobj_map.h kobject.h kobject_api.h kobject_ns.h kprobes.h kref.h kref_api.h ks0108.h ks8842.h ks8851_mll.h ksm.h kstack_erase.h kstrtox.h kthread.h ktime.h ktime_api.h kvm_dirty_ring.h kvm_host.h kvm_irqfd.h kvm_para.h kvm_types.h l2tp.h lantiq.h lapb.h latencytop.h lcd.h lcm.h leafops.h led-class-flash.h led-class-multicolor.h led-lm3530.h leds-bd2802.h leds-expresswire.h leds-lp3944.h leds-lp3952.h leds-pca9532.h leds-regulator.h leds-ti-lmu-common.h leds.h libata.h libfdt.h libfdt_env.h libgcc.h libnvdimm.h libps2.h license.h limits.h linear_range.h linkage.h linkmode.h linux_logo.h lis3lv02d.h list.h list_bl.h list_lru.h list_nulls.h list_sort.h litex.h livepatch.h livepatch_external.h livepatch_helpers.h livepatch_sched.h liveupdate.h llc.h llist.h llist_api.h local_lock.h local_lock_internal.h lockdep.h lockdep_api.h lockdep_types.h lockref.h log2.h logic_iomem.h logic_pio.h lp.h lru_cache.h lsm_audit.h lsm_count.h lsm_hook_defs.h lsm_hooks.h lwq.h lz4.h lzo.h mISDNdsp.h mISDNhw.h mISDNif.h mailbox_client.h mailbox_controller.h maple.h maple_tree.h marvell_phy.h math.h math64.h mbcache.h mbus.h mc146818rtc.h mc33xs2410.h mc6821.h mcb.h mdev.h mdio-bitbang.h mdio-gpio.h mdio-mux.h mdio.h mei_aux.h mei_cl_bus.h mem_encrypt.h memblock.h memcontrol.h memfd.h memory-failure.h memory-tiers.h memory.h memory_hotplug.h mempolicy.h mempool.h memregion.h memremap.h memstick.h mhi.h mhi_ep.h micrel_phy.h microchipphy.h migrate.h migrate_mode.h mii.h mii_timestamper.h min_heap.h minmax.h misc_cgroup.h miscdevice.h mm.h mm_api.h mm_inline.h mm_types.h mm_types_task.h mman.h mmap_lock.h mmdebug.h mmiotrace.h mmu_context.h mmu_notifier.h mmzone.h mnt_idmapping.h mnt_namespace.h mod_devicetable.h module.h module_signature.h module_symbol.h moduleloader.h moduleparam.h most.h mount.h moxtet.h mpage.h mpi.h mpls.h mpls_iptunnel.h mroute.h mroute6.h mroute_base.h msdos_fs.h msdos_partition.h msg.h msi.h msi_api.h mtio.h mutex.h mutex_api.h mutex_types.h mv643xx_eth.h mv643xx_i2c.h mvebu-pmsu.h mxm-wmi.h namei.h nd.h ndctl.h net.h net_tstamp.h netdev_features.h netdevice.h netdevice_xmit.h netfilter.h netfilter_bridge.h netfilter_defs.h netfilter_ipv4.h netfilter_ipv6.h netfilter_netdev.h netfs.h netlink.h netpoll.h nfs.h nfs3.h nfs4.h nfs_common.h nfs_fs.h nfs_fs_i.h nfs_fs_sb.h nfs_iostat.h nfs_page.h nfs_ssc.h nfs_xdr.h nfsacl.h nfslocalio.h nitro_enclaves.h nl802154.h nls.h nmi.h node.h nodemask.h nodemask_types.h nospec.h notifier.h ns_common.h nsc_gpio.h nsfs.h nsproxy.h nstree.h ntb.h ntb_transport.h nubus.h numa.h numa_memblks.h nvme-auth.h nvme-fc-driver.h nvme-fc.h nvme-keyring.h nvme-rdma.h nvme-tcp.h nvme.h nvmem-consumer.h nvmem-provider.h nvram.h oa_tc6.h objagg.h objpool.h objtool.h objtool_types.h of.h of_address.h of_clk.h of_device.h of_dma.h of_fdt.h of_gpio.h of_graph.h of_iommu.h of_irq.h of_mdio.h of_net.h of_pci.h of_pdt.h of_platform.h of_reserved_mem.h oid_registry.h olpc-ec.h omap-dma.h omap-gpmc.h omap-iommu.h omap-mailbox.h omapfb.h once.h once_lite.h oom.h openvswitch.h osq_lock.h overflow.h packing.h padata.h page-flags-layout.h page-flags.h page-isolation.h page_counter.h page_ext.h page_frag_cache.h page_idle.h page_owner.h page_ref.h page_reporting.h page_table_check.h pageblock-flags.h pagemap.h pagevec.h pagewalk.h panic.h panic_notifier.h papr_scm.h parman.h parport.h parport_pc.h parser.h part_stat.h pata_arasan_cf_data.h patchkey.h path.h pch_dma.h pci-acpi.h pci-ats.h pci-bwctrl.h pci-doe.h pci-ecam.h pci-ep-cfs.h pci-ep-msi.h pci-epc.h pci-epf.h pci-ide.h pci-p2pdma.h pci-pwrctrl.h pci-tph.h pci-tsm.h pci.h pci_hotplug.h pci_ids.h pcie-dwc.h pcs-lynx.h pcs-rzn1-miic.h pe.h peci-cpu.h peci.h percpu-defs.h percpu-refcount.h percpu-rwsem.h percpu.h percpu_counter.h perf_event.h perf_event_api.h perf_regs.h personality.h pfn.h pgalloc.h pgalloc_tag.h pgtable.h pgtable_api.h phonet.h phy.h phy_fixed.h phy_led_triggers.h phy_link_topology.h phylib_stubs.h phylink.h pid.h pid_namespace.h pid_types.h pidfs.h pim.h pipe_fs_i.h pkeys.h pl320-ipc.h platform_device.h platform_profile.h pldmfw.h plist.h plist_types.h pm-trace.h pm.h pm_clock.h pm_domain.h pm_opp.h pm_qos.h pm_runtime.h pm_wakeirq.h pm_wakeup.h pmbus.h pmu.h pnp.h poison.h poll.h polynomial.h posix-clock.h posix-timers.h posix-timers_types.h posix_acl.h posix_acl_xattr.h power_supply.h powercap.h ppp-comp.h ppp_channel.h ppp_defs.h pps_gen_kernel.h pps_kernel.h pr.h prandom.h preempt.h prefetch.h prime_numbers.h printk.h prmt.h proc_fs.h proc_ns.h processor.h profile.h projid.h property.h pruss_driver.h psci.h pseudo_fs.h psi.h psi_types.h psp-platform-access.h psp-sev.h psp-tee.h psp.h pstore.h pstore_blk.h pstore_ram.h pstore_zone.h ptdump.h pti.h ptp_classify.h ptp_clock_kernel.h ptp_kvm.h ptp_mock.h ptp_pch.h ptr_ring.h ptrace.h ptrace_api.h purgatory.h pvclock_gtod.h pwm.h pwm_backlight.h pxa168_eth.h pxa2xx_ssp.h qnx6_fs.h quota.h quotaops.h radix-tree.h raid_class.h ramfs.h random.h randomize_kstack.h range.h ras.h ratelimit.h ratelimit_types.h rational.h rbtree.h rbtree_augmented.h rbtree_latch.h rbtree_types.h rcu_node_tree.h rcu_notifier.h rcu_segcblist.h rcu_sync.h rculist.h rculist_bl.h rculist_nulls.h rcupdate.h rcupdate_trace.h rcupdate_wait.h rcuref.h rcutiny.h rcutree.h rcuwait.h rcuwait_api.h reboot-mode.h reboot.h reciprocal_div.h ref_tracker.h refcount.h refcount_api.h refcount_types.h regmap.h regset.h relay.h remoteproc.h resctrl.h resctrl_types.h reset-controller.h reset.h resource.h resource_ext.h restart_block.h resume_user_mode.h rethook.h rfkill.h rhashtable-types.h rhashtable.h ring_buffer.h rio.h rio_drv.h rio_ids.h rio_regs.h rmap.h rmi.h rndis.h rodata_test.h rolling_buffer.h root_dev.h rpmb.h rpmsg.h rseq.h rseq_entry.h rseq_types.h rslib.h rtc.h rtmutex.h rtnetlink.h rtsx_common.h rtsx_pci.h rtsx_usb.h rv.h rw_hint.h rwbase_rt.h rwlock.h rwlock_api_smp.h rwlock_rt.h rwlock_types.h rwsem.h sbitmap.h scatterlist.h scc.h sched.h sched_clock.h scmi_imx_protocol.h scmi_protocol.h scpi_protocol.h screen_info.h scs.h sctp.h scx200.h scx200_gpio.h seccomp.h seccomp_types.h secretmem.h securebits.h security.h sed-opal-key.h sed-opal.h seg6.h seg6_genl.h seg6_hmac.h seg6_iptunnel.h seg6_local.h selection.h sem.h sem_types.h semaphore.h seq_buf.h seq_file.h seq_file_net.h seqlock.h seqlock_api.h seqlock_types.h serdev.h serial.h serial_8250.h serial_bcm63xx.h serial_core.h serial_s3c.h serial_sci.h serio.h set_memory.h sfp.h sh_clk.h sh_dma.h sh_eth.h sh_intc.h sh_timer.h shdma-base.h shm.h shmem_fs.h shrinker.h signal.h signal_types.h signalfd.h siox.h siphash.h sizes.h skb_array.h skbuff.h skbuff_ref.h skmsg.h slab.h slimbus.h sm501-regs.h sm501.h smc91x.h smp.h smp_types.h smpboot.h smsc911x.h smscphy.h sock_diag.h socket.h sockptr.h softirq.h sonet.h sonypi.h sort.h sound.h soundcard.h spinlock.h spinlock_api.h spinlock_api_smp.h spinlock_api_up.h spinlock_rt.h spinlock_types.h spinlock_types_raw.h spinlock_types_up.h spinlock_up.h splice.h spmi.h sprintf.h sram.h srcu.h srcutiny.h srcutree.h ssbi.h stackdepot.h stackprotector.h stacktrace.h start_kernel.h stat.h statfs.h static_call.h static_call_types.h static_key.h stdarg.h stddef.h stm.h stmmac.h stmp3xxx_rtc_wdt.h stmp_device.h stop_machine.h string.h string_choices.h string_helpers.h stringhash.h stringify.h sungem_phy.h sunserialcore.h sunxi-rsb.h surface_acpi_notify.h suspend.h svga.h sw842.h swab.h swait.h swait_api.h swap.h swap_cgroup.h swapfile.h swapops.h swiotlb.h switchtec.h sxgbe_platform.h sync_core.h sync_file.h synclink.h sys.h sys_info.h sys_soc.h syscall_user_dispatch.h syscall_user_dispatch_types.h syscalls.h syscalls_api.h syscore_ops.h sysctl.h sysfb.h sysfs.h syslog.h sysrq.h t10-pi.h task_io_accounting.h task_io_accounting_ops.h task_work.h taskstats_kern.h tboot.h tc.h tcp.h tee_core.h tee_drv.h tegra-icc.h termios_internal.h text-patching.h textsearch.h textsearch_fsm.h thermal.h thread_info.h threads.h thunderbolt.h ti-emif-sram.h ti_wilink_st.h tick.h tifm.h timb_dma.h timb_gpio.h time.h time32.h time64.h time_namespace.h timecounter.h timekeeper_internal.h timekeeping.h timer.h timer_types.h timerfd.h timeriomem-rng.h timerqueue.h timerqueue_types.h timex.h tnum.h topology.h torture.h toshiba.h tpm.h tpm_command.h tpm_eventlog.h tpm_svsm.h trace.h trace_clock.h trace_events.h trace_recursion.h trace_seq.h tracefs.h tracepoint-defs.h tracepoint.h transport_class.h ts-nbus.h tsacct_kern.h tsm-mr.h tsm.h tty.h tty_buffer.h tty_driver.h tty_flip.h tty_ldisc.h tty_port.h turris-omnia-mcu-interface.h turris-signing-key.h typecheck.h types.h u64_stats_sync.h u64_stats_sync_api.h uacce.h uaccess.h ubsan.h ucopysize.h ucs2_string.h udp.h uidgid.h uidgid_types.h uio.h uio_driver.h umh.h unaligned.h unicode.h union_find.h units.h unroll.h unwind_deferred.h unwind_deferred_types.h unwind_user.h unwind_user_types.h uprobes.h usb.h usb_usual.h usbdevice_fs.h user-return-notifier.h user.h user_events.h user_namespace.h userfaultfd_k.h util_macros.h uts.h uts_namespace.h utsname.h uuid.h vbox_utils.h vdpa.h vdso_datastore.h verification.h vermagic.h vexpress.h vfio.h vfio_pci_core.h vfs.h vfsdebug.h vga_switcheroo.h vgaarb.h vhost_iotlb.h via-core.h via.h via_i2c.h videodev2.h virtio.h virtio_anchor.h virtio_byteorder.h virtio_caif.h virtio_config.h virtio_dma_buf.h virtio_features.h virtio_net.h virtio_pci_admin.h virtio_pci_legacy.h virtio_pci_modern.h virtio_ring.h virtio_vsock.h vm_event_item.h vmalloc.h vmcore_info.h vmpressure.h vmstat.h vmw_vmci_api.h vmw_vmci_defs.h vringh.h vt.h vt_buffer.h vt_kern.h vtime.h w1.h wait.h wait_api.h wait_bit.h watch_queue.h watchdog.h win_minmax.h wireless.h wkup_m3_ipc.h wm97xx.h wmi.h wordpart.h workqueue.h workqueue_api.h workqueue_types.h writeback.h ww_mutex.h wwan.h xarray.h xattr.h xxhash.h xz.h yam.h zconf.h zlib.h zorro.h zsmalloc.h zstd.h zstd_errors.h zstd_lib.h zswap.h zutil.h] Subdirs:[amba atomic avf bcma bnxt bus byteorder can cdx ceph clk comedi crush decompress device dma dma-buf dsa extcon firmware fpga framer fs fsl generic_pt gpio greybus habanalabs hsi i3c iio input io_uring irqchip isdn kho lockd lsm mailbox mdio memory mfd misc mlx4 mlx5 mmc mtd mux net netfilter netfilter_arp netfilter_bridge netfilter_ipv4 netfilter_ipv6 ns pcs pds perf phy pinctrl platform_data power pse-pd pwrseq qat qed raid raspberrypi regulator remoteproc reset rpmsg rtc sched soc soundwire spi ssb sunrpc surface_aggregator ulpi unaligned usb]]

38/4 2026/01/29 19:34 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 68379
							output: 24
							thoughts: 61
						

Thoughts:
**Locating the I2C Definitions**

Okay, so I'm trying to pin down where these I2C definitions are. My initial guess is that they're hanging out in either `i2c-smbus.h` or `i2c.h`. I recall seeing that `drivers/hid/hid-ft260.c` includes `linux/i2c.h`, which is a good clue. I think I will check `include/linux/i2c.h` and go from there.



39/4 2026/01/29 19:34 tool read-file
0m Args:
map[File:include/linux/i2c.h]

Results:
map[Contents:/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * i2c.h - definitions for the Linux i2c bus interface
 * Copyright (C) 1995-2000 Simon G. Vogl
 * Copyright (C) 2013-2019 Wolfram Sang <wsa@kernel.org>
 *
 * With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> and
 * Frodo Looijaard <frodol@dds.nl>
 */
#ifndef _LINUX_I2C_H
#define _LINUX_I2C_H

#include <linux/acpi.h>		/* for acpi_handle */
#include <linux/bits.h>
#include <linux/mod_devicetable.h>
#include <linux/device.h>	/* for struct device */
#include <linux/sched.h>	/* for completion */
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/rtmutex.h>
#include <linux/irqdomain.h>		/* for Host Notify IRQ */
#include <linux/of.h>		/* for struct device_node */
#include <linux/swab.h>		/* for swab16 */
#include <uapi/linux/i2c.h>

extern const struct bus_type i2c_bus_type;
extern const struct device_type i2c_adapter_type;
extern const struct device_type i2c_client_type;

/* --- General options ------------------------------------------------	*/

struct i2c_msg;
struct i2c_adapter;
struct i2c_client;
struct i2c_driver;
struct i2c_device_identity;
union i2c_smbus_data;
struct i2c_board_info;
enum i2c_slave_event;
typedef int (*i2c_slave_cb_t)(struct i2c_client *client,
			      enum i2c_slave_event event, u8 *val);

/* I2C Frequency Modes */
#define I2C_MAX_STANDARD_MODE_FREQ	100000
#define I2C_MAX_FAST_MODE_FREQ		400000
#define I2C_MAX_FAST_MODE_PLUS_FREQ	1000000
#define I2C_MAX_TURBO_MODE_FREQ		1400000
#define I2C_MAX_HIGH_SPEED_MODE_FREQ	3400000
#define I2C_MAX_ULTRA_FAST_MODE_FREQ	5000000

struct module;
struct property_entry;

#if IS_ENABLED(CONFIG_I2C)
/* Return the Frequency mode string based on the bus frequency */
const char *i2c_freq_mode_string(u32 bus_freq_hz);

/*
 * The master routines are the ones normally used to transmit data to devices
 * on a bus (or read from them). Apart from two basic transfer functions to
 * transmit one message at a time, a more complex version can be used to
 * transmit an arbitrary number of messages without interruption.
 * @count must be less than 64k since msg.len is u16.
 */
int i2c_transfer_buffer_flags(const struct i2c_client *client,
			      char *buf, int count, u16 flags);

/**
 * i2c_master_recv - issue a single I2C message in master receive mode
 * @client: Handle to slave device
 * @buf: Where to store data read from slave
 * @count: How many bytes to read, must be less than 64k since msg.len is u16
 *
 * Returns negative errno, or else the number of bytes read.
 */
static inline int i2c_master_recv(const struct i2c_client *client,
				  char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};

/**
 * i2c_master_recv_dmasafe - issue a single I2C message in master receive mode
 *			     using a DMA safe buffer
 * @client: Handle to slave device
 * @buf: Where to store data read from slave, must be safe to use with DMA
 * @count: How many bytes to read, must be less than 64k since msg.len is u16
 *
 * Returns negative errno, or else the number of bytes read.
 */
static inline int i2c_master_recv_dmasafe(const struct i2c_client *client,
					  char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, buf, count,
					 I2C_M_RD | I2C_M_DMA_SAFE);
};

/**
 * i2c_master_send - issue a single I2C message in master transmit mode
 * @client: Handle to slave device
 * @buf: Data that will be written to the slave
 * @count: How many bytes to write, must be less than 64k since msg.len is u16
 *
 * Returns negative errno, or else the number of bytes written.
 */
static inline int i2c_master_send(const struct i2c_client *client,
				  const char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};

/**
 * i2c_master_send_dmasafe - issue a single I2C message in master transmit mode
 *			     using a DMA safe buffer
 * @client: Handle to slave device
 * @buf: Data that will be written to the slave, must be safe to use with DMA
 * @count: How many bytes to write, must be less than 64k since msg.len is u16
 *
 * Returns negative errno, or else the number of bytes written.
 */
static inline int i2c_master_send_dmasafe(const struct i2c_client *client,
					  const char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, (char *)buf, count,
					 I2C_M_DMA_SAFE);
};

/* Transfer num messages.
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* Unlocked flavor */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

/* This is the very generalized SMBus access routine. You probably do not
   want to use this, though; one of the functions below may be much easier,
   and probably just as fast.
   Note that we use i2c_adapter here, because you do not need a specific
   smbus adapter to call this function. */
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
		   unsigned short flags, char read_write, u8 command,
		   int protocol, union i2c_smbus_data *data);

/* Unlocked flavor */
s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
		     unsigned short flags, char read_write, u8 command,
		     int protocol, union i2c_smbus_data *data);

/* Now follow the 'nice' access routines. These also document the calling
   conventions of i2c_smbus_xfer. */

u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count);
s32 i2c_smbus_read_byte(const struct i2c_client *client);
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
			      u8 command, u8 value);
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(const struct i2c_client *client,
			      u8 command, u16 value);

static inline s32
i2c_smbus_read_word_swapped(const struct i2c_client *client, u8 command)
{
	s32 value = i2c_smbus_read_word_data(client, command);

	return (value < 0) ? value : swab16(value);
}

static inline s32
i2c_smbus_write_word_swapped(const struct i2c_client *client,
			     u8 command, u16 value)
{
	return i2c_smbus_write_word_data(client, command, swab16(value));
}

/* Returns the number of read bytes */
s32 i2c_smbus_read_block_data(const struct i2c_client *client,
			      u8 command, u8 *values);
s32 i2c_smbus_write_block_data(const struct i2c_client *client,
			       u8 command, u8 length, const u8 *values);
/* Returns the number of read bytes */
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
				  u8 command, u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
				   u8 command, u8 length, const u8 *values);
s32 i2c_smbus_read_i2c_block_data_or_emulated(const struct i2c_client *client,
					      u8 command, u8 length,
					      u8 *values);
int i2c_get_device_id(const struct i2c_client *client,
		      struct i2c_device_identity *id);
const struct i2c_device_id *i2c_client_get_device_id(const struct i2c_client *client);
#endif /* I2C */

/**
 * struct i2c_device_identity - i2c client device identification
 * @manufacturer_id: 0 - 4095, database maintained by NXP
 * @part_id: 0 - 511, according to manufacturer
 * @die_revision: 0 - 7, according to manufacturer
 */
struct i2c_device_identity {
	u16 manufacturer_id;
#define I2C_DEVICE_ID_NXP_SEMICONDUCTORS                0
#define I2C_DEVICE_ID_NXP_SEMICONDUCTORS_1              1
#define I2C_DEVICE_ID_NXP_SEMICONDUCTORS_2              2
#define I2C_DEVICE_ID_NXP_SEMICONDUCTORS_3              3
#define I2C_DEVICE_ID_RAMTRON_INTERNATIONAL             4
#define I2C_DEVICE_ID_ANALOG_DEVICES                    5
#define I2C_DEVICE_ID_STMICROELECTRONICS                6
#define I2C_DEVICE_ID_ON_SEMICONDUCTOR                  7
#define I2C_DEVICE_ID_SPRINTEK_CORPORATION              8
#define I2C_DEVICE_ID_ESPROS_PHOTONICS_AG               9
#define I2C_DEVICE_ID_FUJITSU_SEMICONDUCTOR            10
#define I2C_DEVICE_ID_FLIR                             11
#define I2C_DEVICE_ID_O2MICRO                          12
#define I2C_DEVICE_ID_ATMEL                            13
#define I2C_DEVICE_ID_NONE                         0xffff
	u16 part_id;
	u8 die_revision;
};

enum i2c_alert_protocol {
	I2C_PROTOCOL_SMBUS_ALERT,
	I2C_PROTOCOL_SMBUS_HOST_NOTIFY,
};

/**
 * enum i2c_driver_flags - Flags for an I2C device driver
 *
 * @I2C_DRV_ACPI_WAIVE_D0_PROBE: Don't put the device in D0 state for probe
 */
enum i2c_driver_flags {
	I2C_DRV_ACPI_WAIVE_D0_PROBE = BIT(0),
};

/**
 * struct i2c_driver - represent an I2C device driver
 * @class: What kind of i2c device we instantiate (for detect)
 * @probe: Callback for device binding
 * @remove: Callback for device unbinding
 * @shutdown: Callback for device shutdown
 * @alert: Alert callback, for example for the SMBus alert protocol
 * @command: Callback for bus-wide signaling (optional)
 * @driver: Device driver model driver
 * @id_table: List of I2C devices supported by this driver
 * @detect: Callback for device detection
 * @address_list: The I2C addresses to probe (for detect)
 * @clients: List of detected clients we created (for i2c-core use only)
 * @flags: A bitmask of flags defined in &enum i2c_driver_flags
 *
 * The driver.owner field should be set to the module owner of this driver.
 * The driver.name field should be set to the name of this driver.
 *
 * For automatic device detection, both @detect and @address_list must
 * be defined. @class should also be set, otherwise only devices forced
 * with module parameters will be created. The detect function must
 * fill at least the name field of the i2c_board_info structure it is
 * handed upon successful detection, and possibly also the flags field.
 *
 * If @detect is missing, the driver will still work fine for enumerated
 * devices. Detected devices simply won't be supported. This is expected
 * for the many I2C/SMBus devices which can't be detected reliably, and
 * the ones which can always be enumerated in practice.
 *
 * The i2c_client structure which is handed to the @detect callback is
 * not a real i2c_client. It is initialized just enough so that you can
 * call i2c_smbus_read_byte_data and friends on it. Don't do anything
 * else with it. In particular, calling dev_dbg and friends on it is
 * not allowed.
 */
struct i2c_driver {
	unsigned int class;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client);
	void (*remove)(struct i2c_client *client);


	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *client);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 * For the SMBus Host Notify protocol, the data corresponds to the
	 * 16-bit payload data reported by the slave device acting as master.
	 */
	void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,
		      unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
	const unsigned short *address_list;
	struct list_head clients;

	u32 flags;
};
#define to_i2c_driver(d) container_of_const(d, struct i2c_driver, driver)

/**
 * struct i2c_client - represent an I2C slave device
 * @flags: see I2C_CLIENT_* for possible flags
 * @addr: Address used on the I2C bus connected to the parent adapter.
 * @name: Indicates the type of the device, usually a chip name that's
 *	generic enough to hide second-sourcing and compatible revisions.
 * @adapter: manages the bus segment hosting this I2C device
 * @dev: Driver model device node for the slave.
 * @init_irq: IRQ that was set at initialization
 * @irq: indicates the IRQ generated by this device (if any)
 * @detected: member of an i2c_driver.clients list or i2c-core's
 *	userspace_devices list
 * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
 *	calls it to pass on slave events to the slave driver.
 * @devres_group_id: id of the devres group that will be created for resources
 *	acquired when probing this device.
 * @debugfs: pointer to the debugfs subdirectory which the I2C core created
 *	for this client.
 *
 * An i2c_client identifies a single device (i.e. chip) connected to an
 * i2c bus. The behaviour exposed to Linux is defined by the driver
 * managing the device.
 */
struct i2c_client {
	unsigned short flags;		/* div., see below		*/
#define I2C_CLIENT_PEC		0x04	/* Use Packet Error Checking */
#define I2C_CLIENT_TEN		0x10	/* we have a ten bit chip address */
					/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE	0x20	/* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY	0x40	/* We want to use I2C host notify */
#define I2C_CLIENT_WAKE		0x80	/* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB		0x9000	/* Use Omnivision SCCB protocol */
					/* Must match I2C_M_STOP|IGNORE_NAK */

	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int init_irq;			/* irq set at initialization	*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
	void *devres_group_id;		/* ID of probe devres group	*/
	struct dentry *debugfs;		/* per-client debugfs dir	*/
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)

struct i2c_adapter *i2c_verify_adapter(struct device *dev);
const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
					 const struct i2c_client *client);

const void *i2c_get_match_data(const struct i2c_client *client);

static inline struct i2c_client *kobj_to_i2c_client(struct kobject *kobj)
{
	struct device * const dev = kobj_to_dev(kobj);
	return to_i2c_client(dev);
}

static inline void *i2c_get_clientdata(const struct i2c_client *client)
{
	return dev_get_drvdata(&client->dev);
}

static inline void i2c_set_clientdata(struct i2c_client *client, void *data)
{
	dev_set_drvdata(&client->dev, data);
}

/* I2C slave support */

enum i2c_slave_event {
	I2C_SLAVE_READ_REQUESTED,
	I2C_SLAVE_WRITE_REQUESTED,
	I2C_SLAVE_READ_PROCESSED,
	I2C_SLAVE_WRITE_RECEIVED,
	I2C_SLAVE_STOP,
};

int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb);
int i2c_slave_unregister(struct i2c_client *client);
int i2c_slave_event(struct i2c_client *client,
		    enum i2c_slave_event event, u8 *val);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
bool i2c_detect_slave_mode(struct device *dev);
#else
static inline bool i2c_detect_slave_mode(struct device *dev) { return false; }
#endif

/**
 * struct i2c_board_info - template for device creation
 * @type: chip type, to initialize i2c_client.name
 * @flags: to initialize i2c_client.flags
 * @addr: stored in i2c_client.addr
 * @dev_name: Overrides the default <busnr>-<addr> dev_name if set
 * @platform_data: stored in i2c_client.dev.platform_data
 * @fwnode: device node supplied by the platform firmware
 * @swnode: software node for the device
 * @resources: resources associated with the device
 * @num_resources: number of resources in the @resources array
 * @irq: stored in i2c_client.irq
 *
 * I2C doesn't actually support hardware probing, although controllers and
 * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
 * a device at a given address.  Drivers commonly need more information than
 * that, such as chip type, configuration, associated IRQ, and so on.
 *
 * i2c_board_info is used to build tables of information listing I2C devices
 * that are present.  This information is used to grow the driver model tree.
 * For mainboards this is done statically using i2c_register_board_info();
 * bus numbers identify adapters that aren't yet available.  For add-on boards,
 * i2c_new_client_device() does this dynamically with the adapter already known.
 */
struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	const char	*dev_name;
	void		*platform_data;
	struct fwnode_handle *fwnode;
	const struct software_node *swnode;
	const struct resource *resources;
	unsigned int	num_resources;
	int		irq;
};

/**
 * I2C_BOARD_INFO - macro used to list an i2c device and its address
 * @dev_type: identifies the device type
 * @dev_addr: the device's address on the bus.
 *
 * This macro initializes essential fields of a struct i2c_board_info,
 * declaring what has been provided on a particular board.  Optional
 * fields (such as associated irq, or device-specific platform_data)
 * are provided using conventional syntax.
 */
#define I2C_BOARD_INFO(dev_type, dev_addr) \
	.type = dev_type, .addr = (dev_addr)


#if IS_ENABLED(CONFIG_I2C)
/*
 * Add-on boards should register/unregister their devices; e.g. a board
 * with integrated I2C, a config eeprom, sensors, and a codec that's
 * used in conjunction with the primary hardware.
 */
struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

/* If you don't know the exact address of an I2C device, use this variant
 * instead, which can probe for device presence in a list of possible
 * addresses. The "probe" callback function is optional. If it is provided,
 * it must return 1 on successful probe, 0 otherwise. If it is not provided,
 * a default probing method is used.
 */
struct i2c_client *
i2c_new_scanned_device(struct i2c_adapter *adap,
		       struct i2c_board_info *info,
		       unsigned short const *addr_list,
		       int (*probe)(struct i2c_adapter *adap, unsigned short addr));

/* Common custom probe functions */
int i2c_probe_func_quick_read(struct i2c_adapter *adap, unsigned short addr);

struct i2c_client *
i2c_new_dummy_device(struct i2c_adapter *adapter, u16 address);

struct i2c_client *
devm_i2c_new_dummy_device(struct device *dev, struct i2c_adapter *adap, u16 address);

struct i2c_client *
i2c_new_ancillary_device(struct i2c_client *client,
			 const char *name,
			 u16 default_addr);

void i2c_unregister_device(struct i2c_client *client);

struct i2c_client *i2c_verify_client(struct device *dev);
#else
static inline struct i2c_client *i2c_verify_client(struct device *dev)
{
	return NULL;
}
#endif /* I2C */

/* Mainboard arch_initcall() code should register all its I2C devices.
 * This is done at arch_initcall time, before declaring any i2c adapters.
 * Modules for add-on boards must use other calls.
 */
#ifdef CONFIG_I2C_BOARDINFO
int
i2c_register_board_info(int busnum, struct i2c_board_info const *info,
			unsigned n);
#else
static inline int
i2c_register_board_info(int busnum, struct i2c_board_info const *info,
			unsigned n)
{
	return 0;
}
#endif /* I2C_BOARDINFO */

/**
 * struct i2c_algorithm - represent I2C transfer methods
 * @xfer: Transfer a given number of messages defined by the msgs array via
 *   the specified adapter.
 * @xfer_atomic: Same as @xfer. Yet, only using atomic context so e.g. PMICs
 *   can be accessed very late before shutdown. Optional.
 * @smbus_xfer: Issue SMBus transactions to the given I2C adapter. If this
 *   is not present, then the bus layer will try and convert the SMBus calls
 *   into I2C transfers instead.
 * @smbus_xfer_atomic: Same as @smbus_xfer. Yet, only using atomic context
 *   so e.g. PMICs can be accessed very late before shutdown. Optional.
 * @functionality: Return the flags that this algorithm/adapter pair supports
 *   from the ``I2C_FUNC_*`` flags.
 * @reg_target: Register given client to local target mode of this adapter
 * @unreg_target: Unregister given client from local target mode of this adapter
 *
 * @master_xfer: deprecated, use @xfer
 * @master_xfer_atomic: deprecated, use @xfer_atomic
 * @reg_slave: deprecated, use @reg_target
 * @unreg_slave: deprecated, use @unreg_target
 *
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 *
 * The return codes from the ``xfer{_atomic}`` fields should indicate the
 * type of error code that occurred during the transfer, as documented in the
 * Kernel Documentation file Documentation/i2c/fault-codes.rst. Otherwise, the
 * number of messages executed should be returned.
 */
struct i2c_algorithm {
	/*
	 * If an adapter algorithm can't do I2C-level access, set xfer
	 * to NULL. If an adapter algorithm can do SMBus access, set
	 * smbus_xfer. If set to NULL, the SMBus protocol is simulated
	 * using common I2C messages.
	 */
	union {
		int (*xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			    int num);
		int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
				   int num);
	};
	union {
		int (*xfer_atomic)(struct i2c_adapter *adap,
				   struct i2c_msg *msgs, int num);
		int (*master_xfer_atomic)(struct i2c_adapter *adap,
					   struct i2c_msg *msgs, int num);
	};
	int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
			  unsigned short flags, char read_write,
			  u8 command, int size, union i2c_smbus_data *data);
	int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
				 unsigned short flags, char read_write,
				 u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality)(struct i2c_adapter *adap);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	union {
		int (*reg_target)(struct i2c_client *client);
		int (*reg_slave)(struct i2c_client *client);
	};
	union {
		int (*unreg_target)(struct i2c_client *client);
		int (*unreg_slave)(struct i2c_client *client);
	};
#endif
};

/**
 * struct i2c_lock_operations - represent I2C locking operations
 * @lock_bus: Get exclusive access to an I2C bus segment
 * @trylock_bus: Try to get exclusive access to an I2C bus segment
 * @unlock_bus: Release exclusive access to an I2C bus segment
 *
 * The main operations are wrapped by i2c_lock_bus and i2c_unlock_bus.
 */
struct i2c_lock_operations {
	void (*lock_bus)(struct i2c_adapter *adapter, unsigned int flags);
	int (*trylock_bus)(struct i2c_adapter *adapter, unsigned int flags);
	void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags);
};

/**
 * struct i2c_timings - I2C timing information
 * @bus_freq_hz: the bus frequency in Hz
 * @scl_rise_ns: time SCL signal takes to rise in ns; t(r) in the I2C specification
 * @scl_fall_ns: time SCL signal takes to fall in ns; t(f) in the I2C specification
 * @scl_int_delay_ns: time IP core additionally needs to setup SCL in ns
 * @sda_fall_ns: time SDA signal takes to fall in ns; t(f) in the I2C specification
 * @sda_hold_ns: time IP core additionally needs to hold SDA in ns
 * @digital_filter_width_ns: width in ns of spikes on i2c lines that the IP core
 *	digital filter can filter out
 * @analog_filter_cutoff_freq_hz: threshold frequency for the low pass IP core
 *	analog filter
 */
struct i2c_timings {
	u32 bus_freq_hz;
	u32 scl_rise_ns;
	u32 scl_fall_ns;
	u32 scl_int_delay_ns;
	u32 sda_fall_ns;
	u32 sda_hold_ns;
	u32 digital_filter_width_ns;
	u32 analog_filter_cutoff_freq_hz;
};

/**
 * struct i2c_bus_recovery_info - I2C bus recovery information
 * @recover_bus: Recover routine. Either pass driver's recover_bus() routine, or
 *	i2c_generic_scl_recovery().
 * @get_scl: This gets current value of SCL line. Mandatory for generic SCL
 *      recovery. Populated internally for generic GPIO recovery.
 * @set_scl: This sets/clears the SCL line. Mandatory for generic SCL recovery.
 *      Populated internally for generic GPIO recovery.
 * @get_sda: This gets current value of SDA line. This or set_sda() is mandatory
 *	for generic SCL recovery. Populated internally, if sda_gpio is a valid
 *	GPIO, for generic GPIO recovery.
 * @set_sda: This sets/clears the SDA line. This or get_sda() is mandatory for
 *	generic SCL recovery. Populated internally, if sda_gpio is a valid GPIO,
 *	for generic GPIO recovery.
 * @get_bus_free: Returns the bus free state as seen from the IP core in case it
 *	has a more complex internal logic than just reading SDA. Optional.
 * @prepare_recovery: This will be called before starting recovery. Platform may
 *	configure padmux here for SDA/SCL line or something else they want.
 * @unprepare_recovery: This will be called after completing recovery. Platform
 *	may configure padmux here for SDA/SCL line or something else they want.
 * @scl_gpiod: gpiod of the SCL line. Only required for GPIO recovery.
 * @sda_gpiod: gpiod of the SDA line. Only required for GPIO recovery.
 * @pinctrl: pinctrl used by GPIO recovery to change the state of the I2C pins.
 *      Optional.
 * @pins_default: default pinctrl state of SCL/SDA lines, when they are assigned
 *      to the I2C bus. Optional. Populated internally for GPIO recovery, if
 *      state with the name PINCTRL_STATE_DEFAULT is found and pinctrl is valid.
 * @pins_gpio: recovery pinctrl state of SCL/SDA lines, when they are used as
 *      GPIOs. Optional. Populated internally for GPIO recovery, if this state
 *      is called "gpio" or "recovery" and pinctrl is valid.
 */
struct i2c_bus_recovery_info {
	int (*recover_bus)(struct i2c_adapter *adap);

	int (*get_scl)(struct i2c_adapter *adap);
	void (*set_scl)(struct i2c_adapter *adap, int val);
	int (*get_sda)(struct i2c_adapter *adap);
	void (*set_sda)(struct i2c_adapter *adap, int val);
	int (*get_bus_free)(struct i2c_adapter *adap);

	void (*prepare_recovery)(struct i2c_adapter *adap);
	void (*unprepare_recovery)(struct i2c_adapter *adap);

	/* gpio recovery */
	struct gpio_desc *scl_gpiod;
	struct gpio_desc *sda_gpiod;
	struct pinctrl *pinctrl;
	struct pinctrl_state *pins_default;
	struct pinctrl_state *pins_gpio;
};

int i2c_recover_bus(struct i2c_adapter *adap);

/* Generic recovery routines */
int i2c_generic_scl_recovery(struct i2c_adapter *adap);

/**
 * struct i2c_adapter_quirks - describe flaws of an i2c adapter
 * @flags: see I2C_AQ_* for possible flags and read below
 * @max_num_msgs: maximum number of messages per transfer
 * @max_write_len: maximum length of a write message
 * @max_read_len: maximum length of a read message
 * @max_comb_1st_msg_len: maximum length of the first msg in a combined message
 * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
 *
 * Note about combined messages: Some I2C controllers can only send one message
 * per transfer, plus something called combined message or write-then-read.
 * This is (usually) a small write message followed by a read message and
 * barely enough to access register based devices like EEPROMs. There is a flag
 * to support this mode. It implies max_num_msg = 2 and does the length checks
 * with max_comb_*_len because combined message mode usually has its own
 * limitations. Because of HW implementations, some controllers can actually do
 * write-then-anything or other variants. To support that, write-then-read has
 * been broken out into smaller bits like write-first and read-second which can
 * be combined as needed.
 */

struct i2c_adapter_quirks {
	u64 flags;
	int max_num_msgs;
	u16 max_write_len;
	u16 max_read_len;
	u16 max_comb_1st_msg_len;
	u16 max_comb_2nd_msg_len;
};

/* enforce max_num_msgs = 2 and use max_comb_*_len for length checks */
#define I2C_AQ_COMB			BIT(0)
/* first combined message must be write */
#define I2C_AQ_COMB_WRITE_FIRST		BIT(1)
/* second combined message must be read */
#define I2C_AQ_COMB_READ_SECOND		BIT(2)
/* both combined messages must have the same target address */
#define I2C_AQ_COMB_SAME_ADDR		BIT(3)
/* convenience macro for typical write-then read case */
#define I2C_AQ_COMB_WRITE_THEN_READ	(I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | \
					 I2C_AQ_COMB_READ_SECOND | I2C_AQ_COMB_SAME_ADDR)
/* clock stretching is not supported */
#define I2C_AQ_NO_CLK_STRETCH		BIT(4)
/* message cannot have length of 0 */
#define I2C_AQ_NO_ZERO_LEN_READ		BIT(5)
#define I2C_AQ_NO_ZERO_LEN_WRITE	BIT(6)
#define I2C_AQ_NO_ZERO_LEN		(I2C_AQ_NO_ZERO_LEN_READ | I2C_AQ_NO_ZERO_LEN_WRITE)
/* adapter cannot do repeated START */
#define I2C_AQ_NO_REP_START		BIT(7)

/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */
	unsigned long locked_flags;	/* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED		0
#define I2C_ALF_SUSPEND_REPORTED	1

	int nr;
	char name[48];
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;

	struct irq_domain *host_notify_domain;
	struct regulator *bus_regulator;

	struct dentry *debugfs;

	/* 7bit address space */
	DECLARE_BITMAP(addrs_in_instantiation, 1 << 7);
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)

static inline void *i2c_get_adapdata(const struct i2c_adapter *adap)
{
	return dev_get_drvdata(&adap->dev);
}

static inline void i2c_set_adapdata(struct i2c_adapter *adap, void *data)
{
	dev_set_drvdata(&adap->dev, data);
}

static inline struct i2c_adapter *
i2c_parent_is_i2c_adapter(const struct i2c_adapter *adapter)
{
#if IS_ENABLED(CONFIG_I2C_MUX)
	struct device *parent = adapter->dev.parent;

	if (parent != NULL && parent->type == &i2c_adapter_type)
		return to_i2c_adapter(parent);
	else
#endif
		return NULL;
}

int i2c_for_each_dev(void *data, int (*fn)(struct device *dev, void *data));

/* Adapter locking functions, exported for shared pin cases */
#define I2C_LOCK_ROOT_ADAPTER BIT(0)
#define I2C_LOCK_SEGMENT      BIT(1)

/**
 * i2c_lock_bus - Get exclusive access to an I2C bus segment
 * @adapter: Target I2C bus segment
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT
 *	locks only this branch in the adapter tree
 */
static inline void
i2c_lock_bus(struct i2c_adapter *adapter, unsigned int flags)
{
	adapter->lock_ops->lock_bus(adapter, flags);
}

/**
 * i2c_trylock_bus - Try to get exclusive access to an I2C bus segment
 * @adapter: Target I2C bus segment
 * @flags: I2C_LOCK_ROOT_ADAPTER tries to locks the root i2c adapter,
 *	I2C_LOCK_SEGMENT tries to lock only this branch in the adapter tree
 *
 * Return: true if the I2C bus segment is locked, false otherwise
 */
static inline int
i2c_trylock_bus(struct i2c_adapter *adapter, unsigned int flags)
{
	return adapter->lock_ops->trylock_bus(adapter, flags);
}

/**
 * i2c_unlock_bus - Release exclusive access to an I2C bus segment
 * @adapter: Target I2C bus segment
 * @flags: I2C_LOCK_ROOT_ADAPTER unlocks the root i2c adapter, I2C_LOCK_SEGMENT
 *	unlocks only this branch in the adapter tree
 */
static inline void
i2c_unlock_bus(struct i2c_adapter *adapter, unsigned int flags)
{
	adapter->lock_ops->unlock_bus(adapter, flags);
}

/**
 * i2c_mark_adapter_suspended - Report suspended state of the adapter to the core
 * @adap: Adapter to mark as suspended
 *
 * When using this helper to mark an adapter as suspended, the core will reject
 * further transfers to this adapter. The usage of this helper is optional but
 * recommended for devices having distinct handlers for system suspend and
 * runtime suspend. More complex devices are free to implement custom solutions
 * to reject transfers when suspended.
 */
static inline void i2c_mark_adapter_suspended(struct i2c_adapter *adap)
{
	i2c_lock_bus(adap, I2C_LOCK_ROOT_ADAPTER);
	set_bit(I2C_ALF_IS_SUSPENDED, &adap->locked_flags);
	i2c_unlock_bus(adap, I2C_LOCK_ROOT_ADAPTER);
}

/**
 * i2c_mark_adapter_resumed - Report resumed state of the adapter to the core
 * @adap: Adapter to mark as resumed
 *
 * When using this helper to mark an adapter as resumed, the core will allow
 * further transfers to this adapter. See also further notes to
 * @i2c_mark_adapter_suspended().
 */
static inline void i2c_mark_adapter_resumed(struct i2c_adapter *adap)
{
	i2c_lock_bus(adap, I2C_LOCK_ROOT_ADAPTER);
	clear_bit(I2C_ALF_IS_SUSPENDED, &adap->locked_flags);
	i2c_unlock_bus(adap, I2C_LOCK_ROOT_ADAPTER);
}

/* i2c adapter classes (bitmask) */
#define I2C_CLASS_HWMON		(1<<0)	/* lm_sensors, ... */
/* Warn users that the adapter doesn't support classes anymore */
#define I2C_CLASS_DEPRECATED	(1<<8)

/* Internal numbers to terminate lists */
#define I2C_CLIENT_END		0xfffeU

/* Construct an I2C_CLIENT_END-terminated array of i2c addresses */
#define I2C_ADDRS(addr, addrs...) \
	((const unsigned short []){ addr, ## addrs, I2C_CLIENT_END })


/* ----- functions exported by i2c.o */

/* administration...
 */
#if IS_ENABLED(CONFIG_I2C)
int i2c_add_adapter(struct i2c_adapter *adap);
int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter);
void i2c_del_adapter(struct i2c_adapter *adap);
int i2c_add_numbered_adapter(struct i2c_adapter *adap);

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

/* use a define to avoid include chaining to get THIS_MODULE */
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

static inline bool i2c_client_has_driver(struct i2c_client *client)
{
	return !IS_ERR_OR_NULL(client) && client->dev.driver;
}

/* call the i2c_client->command() of all attached clients with
 * the given arguments */
void i2c_clients_command(struct i2c_adapter *adap,
			 unsigned int cmd, void *arg);

struct i2c_adapter *i2c_get_adapter(int nr);
void i2c_put_adapter(struct i2c_adapter *adap);
unsigned int i2c_adapter_depth(struct i2c_adapter *adapter);

void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults);

/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{
	return adap->algo->functionality(adap);
}

/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{
	return (func & i2c_get_functionality(adap)) == func;
}

/**
 * i2c_check_quirks() - Function for checking the quirk flags in an i2c adapter
 * @adap: i2c adapter
 * @quirks: quirk flags
 *
 * Return: true if the adapter has all the specified quirk flags, false if not
 */
static inline bool i2c_check_quirks(struct i2c_adapter *adap, u64 quirks)
{
	if (!adap->quirks)
		return false;
	return (adap->quirks->flags & quirks) == quirks;
}

/* Return the adapter number for a specific adapter */
static inline int i2c_adapter_id(struct i2c_adapter *adap)
{
	return adap->nr;
}

static inline u8 i2c_8bit_addr_from_msg(const struct i2c_msg *msg)
{
	return (msg->addr << 1) | (msg->flags & I2C_M_RD);
}

/*
 * 10-bit address
 *   addr_1: 5'b11110 | addr[9:8] | (R/nW)
 *   addr_2: addr[7:0]
 */
static inline u8 i2c_10bit_addr_hi_from_msg(const struct i2c_msg *msg)
{
	return 0xf0 | ((msg->addr & GENMASK(9, 8)) >> 7) | (msg->flags & I2C_M_RD);
}

static inline u8 i2c_10bit_addr_lo_from_msg(const struct i2c_msg *msg)
{
	return msg->addr & GENMASK(7, 0);
}

u8 *i2c_get_dma_safe_msg_buf(struct i2c_msg *msg, unsigned int threshold);
void i2c_put_dma_safe_msg_buf(u8 *buf, struct i2c_msg *msg, bool xferred);

int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr);
/**
 * module_i2c_driver() - Helper macro for registering a modular I2C driver
 * @__i2c_driver: i2c_driver struct
 *
 * Helper macro for I2C drivers which do not do anything special in module
 * init/exit. This eliminates a lot of boilerplate. Each module may only
 * use this macro once, and calling it replaces module_init() and module_exit()
 */
#define module_i2c_driver(__i2c_driver) \
	module_driver(__i2c_driver, i2c_add_driver, \
			i2c_del_driver)

/**
 * builtin_i2c_driver() - Helper macro for registering a builtin I2C driver
 * @__i2c_driver: i2c_driver struct
 *
 * Helper macro for I2C drivers which do not do anything special in their
 * init. This eliminates a lot of boilerplate. Each driver may only
 * use this macro once, and calling it replaces device_initcall().
 */
#define builtin_i2c_driver(__i2c_driver) \
	builtin_driver(__i2c_driver, i2c_add_driver)

/* must call put_device() when done with returned i2c_client device */
struct i2c_client *i2c_find_device_by_fwnode(struct fwnode_handle *fwnode);

/* must call put_device() when done with returned i2c_adapter device */
struct i2c_adapter *i2c_find_adapter_by_fwnode(struct fwnode_handle *fwnode);

/* must call i2c_put_adapter() when done with returned i2c_adapter device */
struct i2c_adapter *i2c_get_adapter_by_fwnode(struct fwnode_handle *fwnode);

#else /* I2C */

static inline struct i2c_client *
i2c_find_device_by_fwnode(struct fwnode_handle *fwnode)
{
	return NULL;
}

static inline struct i2c_adapter *
i2c_find_adapter_by_fwnode(struct fwnode_handle *fwnode)
{
	return NULL;
}

static inline struct i2c_adapter *
i2c_get_adapter_by_fwnode(struct fwnode_handle *fwnode)
{
	return NULL;
}

#endif /* !I2C */

#if IS_ENABLED(CONFIG_OF)
/* must call put_device() when done with returned i2c_client device */
static inline struct i2c_client *of_find_i2c_device_by_node(struct device_node *node)
{
	return i2c_find_device_by_fwnode(of_fwnode_handle(node));
}

/* must call put_device() when done with returned i2c_adapter device */
static inline struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node)
{
	return i2c_find_adapter_by_fwnode(of_fwnode_handle(node));
}

/* must call i2c_put_adapter() when done with returned i2c_adapter device */
static inline struct i2c_adapter *of_get_i2c_adapter_by_node(struct device_node *node)
{
	return i2c_get_adapter_by_fwnode(of_fwnode_handle(node));
}

int of_i2c_get_board_info(struct device *dev, struct device_node *node,
			  struct i2c_board_info *info);

#else

static inline struct i2c_client *of_find_i2c_device_by_node(struct device_node *node)
{
	return NULL;
}

static inline struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node)
{
	return NULL;
}

static inline struct i2c_adapter *of_get_i2c_adapter_by_node(struct device_node *node)
{
	return NULL;
}

static inline int of_i2c_get_board_info(struct device *dev,
					struct device_node *node,
					struct i2c_board_info *info)
{
	return -ENOTSUPP;
}

#endif /* CONFIG_OF */

struct acpi_resource;
struct acpi_resource_i2c_serialbus;

#if IS_REACHABLE(CONFIG_ACPI) && IS_REACHABLE(CONFIG_I2C)
bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares,
			       struct acpi_resource_i2c_serialbus **i2c);
int i2c_acpi_client_count(struct acpi_device *adev);
u32 i2c_acpi_find_bus_speed(struct device *dev);
struct i2c_client *i2c_acpi_new_device_by_fwnode(struct fwnode_handle *fwnode,
						 int index,
						 struct i2c_board_info *info);
struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle);
bool i2c_acpi_waive_d0_probe(struct device *dev);
#else
static inline bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares,
					     struct acpi_resource_i2c_serialbus **i2c)
{
	return false;
}
static inline int i2c_acpi_client_count(struct acpi_device *adev)
{
	return 0;
}
static inline u32 i2c_acpi_find_bus_speed(struct device *dev)
{
	return 0;
}
static inline struct i2c_client *i2c_acpi_new_device_by_fwnode(
					struct fwnode_handle *fwnode, int index,
					struct i2c_board_info *info)
{
	return ERR_PTR(-ENODEV);
}
static inline struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle)
{
	return NULL;
}
static inline bool i2c_acpi_waive_d0_probe(struct device *dev)
{
	return false;
}
#endif /* CONFIG_ACPI */

static inline struct i2c_client *i2c_acpi_new_device(struct device *dev,
						     int index,
						     struct i2c_board_info *info)
{
	return i2c_acpi_new_device_by_fwnode(dev_fwnode(dev), index, info);
}

#endif /* _LINUX_I2C_H */
]

40/4 2026/01/29 19:34 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 82318
							output: 27
							thoughts: 33
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating the I2C Header Inclusion**

Alright, so the code I'm looking at has brought in `uapi/linux/i2c.h`. Right off the bat, that tells me we're dealing with low-level I2C communication within the Linux kernel space, or at least a program intending to use the kernel's I2C interfaces. My first thought is to dive right into that header file. I need to understand exactly which structs, defines, and functions are being leveraged. Specifically, I'll be looking for any indications about the I2C bus I'm expecting to work with, the kinds of messages being constructed, and the data structures passed to the kernel for these I2C transactions. Is it using the standard `i2c_msg` and `i2c_rdwr_ioctl_data` structures? Are there any custom extensions, perhaps vendor-specific commands, defined within the file that are relevant to this particular implementation? That's where I need to start my analysis. This header inclusion is the lynchpin, the place where everything comes together, or at least where it begins.



41/4 2026/01/29 19:34 tool read-file
0m Args:
map[File:include/uapi/linux/i2c.h]

Results:
map[Contents:/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
/*
 * i2c.h - definitions for the I2C bus interface
 *
 * Copyright (C) 1995-2000 Simon G. Vogl
 * With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> and
 * Frodo Looijaard <frodol@dds.nl>
 */

#ifndef _UAPI_LINUX_I2C_H
#define _UAPI_LINUX_I2C_H

#include <linux/types.h>

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 *
 * @addr: Slave address, either 7 or 10 bits. When this is a 10 bit address,
 *   %I2C_M_TEN must be set in @flags and the adapter must support
 *   %I2C_FUNC_10BIT_ADDR.
 *
 * @flags:
 *   Supported by all adapters:
 *   %I2C_M_RD: read data (from slave to master). Guaranteed to be 0x0001! If
 *   not set, the transaction is interpreted as write.
 *
 *   Optional:
 *   %I2C_M_DMA_SAFE: the buffer of this message is DMA safe. Makes only sense
 *     in kernelspace, because userspace buffers are copied anyway
 *
 *   Only if I2C_FUNC_10BIT_ADDR is set:
 *   %I2C_M_TEN: this is a 10 bit chip address
 *
 *   Only if I2C_FUNC_SMBUS_READ_BLOCK_DATA is set:
 *   %I2C_M_RECV_LEN: message length will be first received byte
 *
 *   Only if I2C_FUNC_NOSTART is set:
 *   %I2C_M_NOSTART: skip repeated start sequence
 *
 *   Only if I2C_FUNC_PROTOCOL_MANGLING is set:
 *   %I2C_M_NO_RD_ACK: in a read message, master ACK/NACK bit is skipped
 *   %I2C_M_IGNORE_NAK: treat NACK from client as ACK
 *   %I2C_M_REV_DIR_ADDR: toggles the Rd/Wr bit
 *   %I2C_M_STOP: force a STOP condition after the message
 *
 * @len: Number of data bytes in @buf being read from or written to the I2C
 *   slave address. For read transactions where %I2C_M_RECV_LEN is set, the
 *   caller guarantees that this buffer can hold up to %I2C_SMBUS_BLOCK_MAX
 *   bytes in addition to the initial length byte sent by the slave (plus,
 *   if used, the SMBus PEC); and this value will be incremented by the number
 *   of block data bytes received.
 *
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports %I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they need.
 */
struct i2c_msg {
	__u16 addr;
	__u16 flags;
#define I2C_M_RD		0x0001	/* guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* use only if I2C_FUNC_10BIT_ADDR */
#define I2C_M_DMA_SAFE		0x0200	/* use only in kernel space */
#define I2C_M_RECV_LEN		0x0400	/* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */
#define I2C_M_NO_RD_ACK		0x0800	/* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* use only if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* use only if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;
	__u8 *buf;
};

/* To determine what functionality is present */

#define I2C_FUNC_I2C			0x00000001
#define I2C_FUNC_10BIT_ADDR		0x00000002 /* required for I2C_M_TEN */
#define I2C_FUNC_PROTOCOL_MANGLING	0x00000004 /* required for I2C_M_IGNORE_NAK etc. */
#define I2C_FUNC_SMBUS_PEC		0x00000008
#define I2C_FUNC_NOSTART		0x00000010 /* required for I2C_M_NOSTART */
#define I2C_FUNC_SLAVE			0x00000020
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL	0x00008000 /* SMBus 2.0 or later */
#define I2C_FUNC_SMBUS_QUICK		0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE	0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE	0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA	0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA	0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA	0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA	0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL	0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA	0x01000000 /* required for I2C_M_RECV_LEN */
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK	0x04000000 /* I2C-like block xfer  */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK	0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_HOST_NOTIFY	0x10000000 /* SMBus 2.0 or later */

#define I2C_FUNC_SMBUS_BYTE		(I2C_FUNC_SMBUS_READ_BYTE | \
					 I2C_FUNC_SMBUS_WRITE_BYTE)
#define I2C_FUNC_SMBUS_BYTE_DATA	(I2C_FUNC_SMBUS_READ_BYTE_DATA | \
					 I2C_FUNC_SMBUS_WRITE_BYTE_DATA)
#define I2C_FUNC_SMBUS_WORD_DATA	(I2C_FUNC_SMBUS_READ_WORD_DATA | \
					 I2C_FUNC_SMBUS_WRITE_WORD_DATA)
#define I2C_FUNC_SMBUS_BLOCK_DATA	(I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
					 I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)
#define I2C_FUNC_SMBUS_I2C_BLOCK	(I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
					 I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)

#define I2C_FUNC_SMBUS_EMUL		(I2C_FUNC_SMBUS_QUICK | \
					 I2C_FUNC_SMBUS_BYTE | \
					 I2C_FUNC_SMBUS_BYTE_DATA | \
					 I2C_FUNC_SMBUS_WORD_DATA | \
					 I2C_FUNC_SMBUS_PROC_CALL | \
					 I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \
					 I2C_FUNC_SMBUS_I2C_BLOCK | \
					 I2C_FUNC_SMBUS_PEC)

/* if I2C_M_RECV_LEN is also supported */
#define I2C_FUNC_SMBUS_EMUL_ALL		(I2C_FUNC_SMBUS_EMUL | \
					 I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
					 I2C_FUNC_SMBUS_BLOCK_PROC_CALL)

/*
 * Data for SMBus Messages
 */
#define I2C_SMBUS_BLOCK_MAX	32	/* As specified in SMBus standard */
union i2c_smbus_data {
	__u8 byte;
	__u16 word;
	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
			       /* and one more for user-space compatibility */
};

/* i2c_smbus_xfer read or write markers */
#define I2C_SMBUS_READ	1
#define I2C_SMBUS_WRITE	0

/* SMBus transaction types (size parameter in the above functions)
   Note: these no longer correspond to the (arbitrary) PIIX4 internal codes! */
#define I2C_SMBUS_QUICK		    0
#define I2C_SMBUS_BYTE		    1
#define I2C_SMBUS_BYTE_DATA	    2
#define I2C_SMBUS_WORD_DATA	    3
#define I2C_SMBUS_PROC_CALL	    4
#define I2C_SMBUS_BLOCK_DATA	    5
#define I2C_SMBUS_I2C_BLOCK_BROKEN  6
#define I2C_SMBUS_BLOCK_PROC_CALL   7		/* SMBus 2.0 */
#define I2C_SMBUS_I2C_BLOCK_DATA    8

#endif /* _UAPI_LINUX_I2C_H */
]

42/4 2026/01/29 19:34 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 85496
							output: 551
							thoughts: 1758
						

Thoughts:
**Analysis of the FT260 SMBus Block Transfer Vulnerability**

Okay, so the issue here is pretty clear. `I2C_SMBUS_BLOCK_MAX` is defined as 32, as expected, in `include/uapi/linux/i2c.h`. Good, that validates the core of the fix. I need to formulate a complete answer.

The core of the problem lies in a stack-out-of-bounds read/write within the `ft260_smbus_xfer` function. Specifically, it's the `data->block[0]` value, coming from user space via `ioctl`, that controls the length of block transfers. The vulnerability occurs when a malicious actor crafts an `ioctl` call with a large value (e.g., 42), which exceeds the buffer size allocated on the stack for `union i2c_smbus_data`, which is 34 bytes. This results in reads/writes beyond the allocated memory.

The fix I've applied involves validating `data->block[0]` against `I2C_SMBUS_BLOCK_MAX` (32) for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`. This will prevent the out-of-bounds access. I've double-checked my patch in `drivers/hid/hid-ft260.c`:

```c
	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
...
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
...
```

That looks correct. The `ft260_smbus_xfer` function is `static int`, the `smbus_exit` label exists and `ret` is defined – consistency is key.

Before I say I'm completely confident, let's address a concern: can the `data` pointer be NULL? In the `i2cdev_ioctl_smbus` function, `data` is checked, so user-space shouldn't pass a NULL pointer, but it is important to confirm there aren't situations in kernel space where it might happen. Looking at the `i2c_smbus_xfer` definition confirms this potential, but the `ioctl` from userspace ensures that `data` is not NULL. The `i2c_smbus_read_block_data` and `i2c_smbus_write_block_data` functions *do* pass a valid pointer to the `data` structure when performing block data transfers. The dereference `data->block[0]` in the case of `I2C_SMBUS_BLOCK_DATA` is safe.

Let's do one last check with `I2C_SMBUS_BYTE`.

```c
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
```

Here for `READ`, `data` is dereferenced. If `data` was NULL, `ft260` would crash on `I2C_SMBUS_BYTE` READ too.
So `ft260` *assumes* `data` is not NULL.

I'm now confident that my solution addresses the vulnerability.

One side note, the KASAN report specifies:
`ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486`.

Let me see where line 486 is. Without line numbers, it's difficult to verify the exact line, but it should be where data is copied.

```c
	if (data_len > 0)
		memcpy(&rep


43/3 2026/01/29 19:35 action test-patch
10m Results:
map[PatchDiff:diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
 TestError:]

44/1 2026/01/29 19:45 action get-maintainers
0m Results:
map[Recipients:[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]]

45/1 2026/01/29 19:45 agent description-generator
0m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with writing a commit description for
a kernel bug fixing commit. The description should start with a one-line summary,
and then include description of the bug being fixed, and how it's fixed by the provided patch.
Your final reply should contain only the text of the commit description.
Phrase the one-line summary so that it is not longer than 72 characters.
The rest of the description must be word-wrapped at 72 characters.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

CPU: 0 UID: 0 PID: 6009 Comm: syz-executor165 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1925b454f9
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffee7bc0ab8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffee7bc0c48 RCX: 00007f1925b454f9
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffee7bc0ae0
R13: 00007ffee7bc0ad0 R14: 00007f1925bd8c40 R15: 0000000000000000
 </TASK>

The buggy address belongs to stack of task syz-executor165/6009
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90002410000 allocated at copy_process+0x4e7/0x3950 kernel/fork.c:2052
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x213ff
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000084ffc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 6007, tgid 6007 (syz-executor165), ts 90494270799, free_ts 87604917166
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5954 tgid 5954 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 getname_maybe_null include/linux/fs.h:2505 [inline]
 vfs_fstatat+0x43/0x170 fs/stat.c:370
 __do_sys_newfstatat fs/stat.c:542 [inline]
 __se_sys_newfstatat fs/stat.c:536 [inline]
 __x64_sys_newfstatat+0x116/0x190 fs/stat.c:536
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002417c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002417c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002417d00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002417d80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002417e00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a missing validation check in the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`.

When handling `I2C_SMBUS_BLOCK_DATA` or `I2C_SMBUS_I2C_BLOCK_DATA` commands, the driver uses the block length provided by the user (in `data->block[0]`) to determine how many bytes to read from the `data->block` buffer. However, it fails to ensure that this length does not exceed the maximum size of the `union i2c_smbus_data` structure, specifically `I2C_SMBUS_BLOCK_MAX` (32 bytes).

The crash flow is as follows:
1.  **User Input**: The `i2cdev_ioctl_smbus` function copies a `union i2c_smbus_data` from user space into a local stack variable `temp` (size 34 bytes). The user can set `temp.block[0]` to a large value, e.g., 42.
2.  **Driver Call**: `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which invokes `ft260_smbus_xfer` passing a pointer to `temp` as `data`.
3.  **Vulnerable Code**: In `ft260_smbus_xfer`, specifically for the `I2C_SMBUS_I2C_BLOCK_DATA` case (indicated by the KASAN report showing the read starting at offset 1 of the object):
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        // ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
    It passes `data->block + 1` as the source and `data->block[0]` (42) as the length.
4.  **Out-of-Bounds Read**: `ft260_smbus_write` performs a `memcpy` reading 42 bytes from `data->block + 1`. Since `temp` is only 34 bytes large, reading 42 bytes starting from offset 1 extends past the end of the stack-allocated structure, triggering the KASAN stack-out-of-bounds error.

**Fix**:
Add a check in `ft260_smbus_xfer` to validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX` before processing block transfers.

```c
	case I2C_SMBUS_BLOCK_DATA:
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
			return -EINVAL;
        // ...
```

The diff of the bug fix is:

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);


Additional description of the patch:

The bug was caused by a missing validation check in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`. When handling `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`, the function uses `data->block[0]` as the length for the transfer without checking if it exceeds the maximum allowed block size (`I2C_SMBUS_BLOCK_MAX`).

A malicious or buggy user space program can pass a large value (e.g., 42) in `data->block[0]` via `ioctl`. This leads to `ft260_smbus_write` (for writes) or `ft260_i2c_read` (for reads) accessing the `data->block` buffer beyond its bounds. The `data` buffer is allocated on the stack (as `union i2c_smbus_data` in `i2cdev_ioctl_smbus`), so this results in a stack-out-of-bounds access.

The fix involves adding a check in `ft260_smbus_xfer` to ensure `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX` (32) before proceeding with the block transfer.

```c
// drivers/hid/hid-ft260.c

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
    // ...
	switch (size) {
    // ...
	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
    // ...
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
    // ...
```



Reply:
HID: ft260: fix stack OOB in ft260_smbus_xfer

The ft260_smbus_xfer() function handles I2C SMBus transfers. When
processing I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA
commands, it uses data->block[0] as the length of the data to be
transferred. This value comes from user space and is not validated
against the maximum allowed block size (I2C_SMBUS_BLOCK_MAX).

If a malicious or buggy user space program provides a length larger
than I2C_SMBUS_BLOCK_MAX (32), it leads to an out-of-bounds read
from the stack-allocated union i2c_smbus_data structure in
ft260_smbus_write().

This issue is detected by KASAN:
  BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0
  Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

Add a check to ensure that data->block[0] does not exceed
I2C_SMBUS_BLOCK_MAX before processing the block transfer.

46/2 2026/01/29 19:45 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 7484
							output: 309
							thoughts: 2936
						

Thoughts:
**Bug Analysis and Commit Message Generation**

Okay, here's how I'm approaching this kernel bug fix commit message.  I have the crash report, root cause, the proposed diff, and other details. My primary concern is to create a clear, concise, and informative commit message, while being accurate.

First, I need to understand the problem. The crash report indicates a KASAN stack-out-of-bounds in `ft260_smbus_write`, specifically within the `drivers/hid/hid-ft260.c` file. The root cause is that the `ft260_smbus_xfer` function, which handles both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` commands, uses a length value (`data->block[0]`) provided from user space via `ioctl`. This value is not validated against a maximum allowed size, `I2C_SMBUS_BLOCK_MAX` (32). A malicious or flawed user space program can then specify a length exceeding `I2C_SMBUS_BLOCK_MAX`, causing a stack-out-of-bounds read in `ft260_smbus_write`.

The fix is straightforward: add a check within `ft260_smbus_xfer()` to ensure that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` for both the block transfer commands, returning `-EINVAL` if it does.

Now, let's craft the commit message.  The one-line summary should be < 72 characters, so that is a prime consideration. My initial thought is "HID: ft260: fix stack OOB in ft260_smbus_xfer".

For the detailed description, I need to follow a structure:
1.  Explain the bug: the unchecked length from userspace causing the OOB read.
2.  Mention the KASAN report, which provides evidence of the problem.
3.  Explain the fix: the length validation against `I2C_SMBUS_BLOCK_MAX`.

Here's the message I've constructed:

```
HID: ft260: fix stack OOB in ft260_smbus_xfer

The ft260_smbus_xfer() function handles I2C SMBus transfers. When
processing I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA
commands, it uses data->block[0] as the length of the data to be
transferred. This value comes from user space and is not validated
against the maximum allowed block size (I2C_SMBUS_BLOCK_MAX).

If a malicious or buggy user space program provides a length larger
than I2C_SMBUS_BLOCK_MAX (32), it leads to an out-of-bounds read
of 42 bytes from the stack-allocated union i2c_smbus_data structure in
ft260_smbus_write().

This issue is detected by KASAN:
  BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0
  Read of size 42 at addr ffffc90002417d21 by task syz-executor165/6009

Add a check to ensure that data->block[0] does not exceed
I2C_SMBUS_BLOCK_MAX before processing the block transfer.
```

I have verified the subject line length (45 chars), and the description length wraps appropriately at 72. The content now accurately reflects the bug, includes details from the KASAN report, and explains the fix. I also confirmed that the fix addresses both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases, per the patch. The commit message looks solid. I have confirmed there is no other mention of this problem in the code base.




LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)