Writing buffers > 8 kB on verdin

I’m trying to integrate a time of flight sensor with the Verdin Plus over i2c. To do this, I need to load 32,768 bytes of data in a single i2c write without a stop condition. The issue is that I can’t seem to transfer over 8 kB of data in one go due to some strictly defined buffer size somewhere. I’ve tried many things, including ioctl calls and the i2c-dev.h library. The 8 kB buffer size limit remains constant. Are there any workarounds?

It seems the source of the problem comes from the Linux kernel’s linux/i2c-dev.c:

For whatever reason, the TwoWire library from Arduino is able to handle this without issues when implemented on a teensy 3.6:

/ // Chunk I2C transactions into limit of 32 bytes (or wireMaxPacketSize)
     uint8_t i2cError = 0;
     uint32_t startSpot = 0;
     uint32_t bytesToSend = bufferSize;
     while (bytesToSend > 0)
     {
         uint32_t len = bytesToSend;
         if (len > (wireMaxPacketSize - 2)) // Allow 2 byte for register address
             len = (wireMaxPacketSize - 2);

         _i2cPort->beginTransmission((uint8_t)_address);
         _i2cPort->write(highByte(registerAddress));
         _i2cPort->write(lowByte(registerAddress));

         // TODO write a subsection of the buffer rather than byte wise
         for (uint16_t x = 0; x < len; x++)
             _i2cPort->write(buffer[startSpot + x]); // Write a portion of the payload to the bus

         i2cError = _i2cPort->endTransmission(); // Release bus because we are writing the address each time
         if (i2cError != 0)
             return (i2cError); // Sensor did not ACK

         startSpot += len; // Move the pointer forward
         bytesToSend -= len;
         registerAddress += len; // Move register address forward
     }
     return (i2cError);

Code from the provided sensor library:

	/* Download FW into VL53L5 */
	status |= WrByte(&(p_dev->platform), 0x7fff, 0x09);
	status |= WrMulti(&(p_dev->platform), 0, (uint8_t *) &VL53L5CX_FIRMWARE[0], 0x8000);
	status |= WrByte(&(p_dev->platform), 0x7fff, 0x0a);
	status |= WrMulti(&(p_dev->platform), 0, (uint8_t *) & VL53L5CX_FIRMWARE[0x8000], 0x8000);
	status |= WrByte(&(p_dev->platform), 0x7fff, 0x0b);
	status |= WrMulti(&(p_dev->platform), 0, (uint8_t *) & VL53L5CX_FIRMWARE[0x10000], 0x5000);
	status |= WrByte(&(p_dev->platform), 0x7fff, 0x01);

My i2c_write function:

		writeData[0] = addConvert.regAddr_8[1];
		writeData[1] = addConvert.regAddr_8[0];
		for(i=0; i<count; i++) writeData[i+2]=data[i];

		// send the bytes
		ret = write(i2c[bus].fd, writeData, count+2);
		// write should have returned the correct # bytes written
		if(unlikely(ret!=(signed)(count+2))){
			fprintf(stderr,"ERROR in rc_i2c_write_bytes, bus wrote %d bytes, expected %zu\n", ret, count+2);
			i2c[bus].lock = old_lock;
			return -1;
		}

I believe that your fixed value 8192 is in linux-toradex\drivers\i2c\i2c-dev.c file:

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;

	if (count > 8192)
		count = 8192;

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

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

	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
	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;

	if (count > 8192)
		count = 8192;

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

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

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

Thanks Fide, I think removing the size limit and recompiling the kernel would probably have worked. I solved the problem when I realized all the data was supposed to be written in a sequential segment of memory. Whenever there is a stop condition, as long as the first two bytes written after the next write call to the device address correspond to the proper memory address, the VL53L5CX time of flight sensor will boot. This is the important bit of code that keeps track of the memory address. It seems that Arduino’s TwoWire library automatically handles this.

      #include <linux/i2c-dev.h>

      union {
	      uint16_t regAddr;
	      uint8_t  regAddr_8[2];
       } addConvert;

       union {
	      uint16_t sentCount;
	      uint8_t  sentCount_8[2];
       } sentCount;

        addConvert.regAddr = registerAddress;

		uint8_t writeData[8192];
		//set up out buffer with converted uint16_t address
		writeData[0] = addConvert.regAddr_8[1];
		writeData[1] = addConvert.regAddr_8[0];

        size_t j = 0, k = 0;
		//max buffer size in kernel = 8192, written here explicitly
		while (j*8190 <= (count-8190) && count >= 8190) {
			for(k=0; k<8190; k++) writeData[k+2]=data[k+j*8190];
			write(i2c[bus].fd, writeData, 8192);

			j++;
			sentCount.sentCount = j*8190;
			writeData[0] = sentCount.sentCount_8[1]; //populate first two indices with start index
			writeData[1] = sentCount.sentCount_8[0];

			std::cout << "Large i2c write of 8192 bytes." << std::endl;
		}

        //take care of last remaining bytes
		if ((count - j*8190) > 0) {
			for(k=0; k<((count - j*8190)); k++) writeData[k+2]=data[k+j*8190];
			write(i2c[bus].fd, writeData, (count-j*8190)+2); //write remaining buffer
			std::cout << "Large i2c write of " << std::dec << (count-j*8190)+2 << " bytes." << std::endl;
		}
1 Like