I2C EEPROM Read/Write Cubieboard 2 Arch Linux - linux

I am trying to read and write to the AT24MAC402 EEPROM over i2c on the Cubieboard 2 using Arch Linux. I am using the i2c-dev library and i2c-tools.
Datasheet:
http://www.atmel.com/images/atmel-8807-seeprom-at24mac402-602-datasheet.pdf
I can successfully write (kind of...) to a chosen address and sequentially write many bites starting at that address. The issues are:
Cannot re-select another address to write once the first address has been selected.
Cannot point the the EEPROM to the location I wish to read from (by dummy-writing), and therefore have almost no real control over the EEPROM.
Upon looking at the datasheet (for hours on end), it looks as if I don't have as much control over the I2C communications as I may need using the i2c-dev library.. It would be great if I could just write X bits or X bytes directly to the EEPROM.
In short, I would like advice on how I can read and write properly to this EEPROM.
char buf[10];
int com_serial;
int failcount;
int i2c_init(char filename[40], int addr)
{
int file;
if ((file = open(filename,O_RDWR)) < 0)
{
printf("Failed to open the bus.");
/* ERROR HANDLING; you can check errno to see what went wrong */
com_serial=0;
exit(1);
}
if (ioctl(file,I2C_SLAVE,addr) < 0)
{
printf("Failed to acquire bus access and/or talk to slave.\n");
/* ERROR HANDLING; you can check errno to see what went wrong */
com_serial=0;
exit(1);
}
return file;
}
int main (int argc, char *argv[]) {
char read_buf[16];
char write_buf[17];
int i;
int file;
file=i2c_init("/dev/i2c-1",0x50); //dev,slavei2caddr
write_buf[0] = 0x00;
write_buf[1] = 'H';
write_buf[2] = 'i';
write_buf[3] = '!';
write(file, write_buf, 4);
//Successfully prints "Hi!" to bytes 0x00 -> 0x02
//Setting EEPROM to point to address 0xA0 to start reading (arbitrary address with known values: all 0xFF)
write_buf[0] = 0xA0;
write(file, write_buf, 1);
//Reading 1 byte from EEPROM, even though there is a '2'; 2 bytes would be '3'
read(file, read_buf, 2);
for (i=1; i<3; i++){
printf("%X", read_buf[i]);
}
//Prints out from address 0x04 to 0x05 instead of 0xA0 to 0xA1
printf("\n");
}

I did work properly using the functions from the linux/i2c-dev.h.
To test the code I get the output generated by i2cdump and put as input to i2c-stub-from-dump tool, it lets you setup one or more fake I2C chips on the i2c-stub bus based on dumps of the chips you want to emulate.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
int i2c_init(const char * i2c_device, const int chip_address)
{
int file;
if ((file = open(i2c_device, O_RDWR)) < 0) {
return -1;
}
if (ioctl(file, I2C_SLAVE, chip_address) < 0) {
close(file);
return -1;
}
return file;
}
int i2c_write(int file, const int data_address, const unsigned char * data, size_t size)
{
return i2c_smbus_write_i2c_block_data(file, data_address, size, data);
}
void i2c_read(int file, const int data_address, unsigned char * data_vector, size_t size)
{
unsigned char reg = data_address;
unsigned int i;
for(i = 0; i < size; ++i, ++reg) {
data_vector[i] = i2c_smbus_read_byte_data(file, reg);
}
}
int main(void) {
char device[] = "/dev/i2c-6";
int address = 0x50;
unsigned char buffer_before[30] = {0};
unsigned char buffer_after[30] = {0};
unsigned char data[] = "Hello World!";
int file;
file = i2c_init(device, address);
if (file > 0) {
i2c_read(file, 0x00, buffer_before, sizeof(data));
i2c_write(file, 0x00, data, sizeof(data));
i2c_read(file, 0x00, buffer_after, sizeof(data));
close (file);
}
printf("data read before write: %s\n", buffer_before);
printf("data read after write: %s\n", buffer_after);
return 0;
}

Related

Zero copy in using vmsplice/splice in Linux

I am trying to get zero copy semantics working in linux using
vmsplice()/splice() but I don't see any performance improvement. This
is on linux 3.10, tried on 3.0.0 and 2.6.32. The following code tries
to do file writes, I have tried network socket writes() also, couldn't
see any improvement.
Can somebody tell what am I doing wrong ?
Has anyone gotten improvement using vmsplice()/splice() in production ?
#include <assert.h>
#include <fcntl.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
const char *filename = "Test-File";
const int block_size = 4 * 1024;
const int file_size = 4 * 1024 * 1024;
using namespace std;
int pipes[2];
vector<char *> file_data;
static int NowUsecs() {
struct timeval tv;
const int err = gettimeofday(&tv, NULL);
assert(err >= 0);
return tv.tv_sec * 1000000LL + tv.tv_usec;
}
void CreateData() {
for (int xx = 0; xx < file_size / block_size; ++xx) {
// The data buffer to fill.
char *data = NULL;
assert(posix_memalign(reinterpret_cast<void **>(&data), 4096, block_size) == 0);
file_data.emplace_back(data);
}
}
int SpliceWrite(int fd, char *buf, int buf_len) {
int len = buf_len;
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = len;
while (len) {
int ret = vmsplice(pipes[1], &iov, 1, SPLICE_F_GIFT);
assert(ret >= 0);
if (!ret)
break;
len -= ret;
if (len) {
auto ptr = static_cast<char *>(iov.iov_base);
ptr += ret;
iov.iov_base = ptr;
iov.iov_len -= ret;
}
}
len = buf_len;
while (len) {
int ret = splice(pipes[0], NULL, fd, NULL, len, SPLICE_F_MOVE);
assert(ret >= 0);
if (!ret)
break;
len -= ret;
}
return 1;
}
int WriteToFile(const char *filename, bool use_splice) {
// Open and write to the file.
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
int fd = open(filename, O_CREAT | O_RDWR, mode);
assert(fd >= 0);
const int start = NowUsecs();
for (int xx = 0; xx < file_size / block_size; ++xx) {
if (use_splice) {
SpliceWrite(fd, file_data[xx], block_size);
} else {
assert(write(fd, file_data[xx], block_size) == block_size);
}
}
const int time = NowUsecs() - start;
// Close file.
assert(close(fd) == 0);
return time;
}
void ValidateData() {
// Open and read from file.
const int fd = open(filename, O_RDWR);
assert(fd >= 0);
char *read_buf = (char *)malloc(block_size);
for (int xx = 0; xx < file_size / block_size; ++xx) {
assert(read(fd, read_buf, block_size) == block_size);
assert(memcmp(read_buf, file_data[xx], block_size) == 0);
}
// Close file.
assert(close(fd) == 0);
assert(unlink(filename) == 0);
}
int main(int argc, char **argv) {
auto res = pipe(pipes);
assert(res == 0);
CreateData();
const int without_splice = WriteToFile(filename, false /* use splice */);
ValidateData();
const int with_splice = WriteToFile(filename, true /* use splice */);
ValidateData();
cout << "TIME WITH SPLICE: " << with_splice << endl;
cout << "TIME WITHOUT SPLICE: " << without_splice << endl;
return 0;
}
I did a proof-of-concept some years ago where I got as 4x speedup using an optimized, specially tailored, vmsplice() code. This was measured against a generic socket/write() based solution. This blog post from natsys-lab echoes my findings. But I believe you need to have the exact right use case to get near this number.
So what are you doing wrong? Primarily I think you are measuring the wrong thing. When writing directly to a file you have 1 system call, which is write(). And you are not actually copying data (except to the kernel). When you have a buffer with data that you want to write to disk, it's not gonna get faster than that.
In you vmsplice/splice setup you are still copying you data into the kernel, but you have a total of 2 system calls vmsplice()+splice() to get it to disk. The speed being identical to write() is probably just a testament to Linux system call speed :-)
A more "fair" setup would be to write one program that read() from stdin and write() the same data to stdout. Write an identical program that simply splice() stdin into a file (or point stdout to a file when you run it). Although this setup might be too simple to really show anything.
Aside: an (undocumented?) feature of vmsplice() is that you can also use to to read data from a pipe. I used this in my old POC. It was basically just an IPC layer based on the idea of passing memory pages around using vmsplice().
Note: NowUsecs() probably overflows the int

Serial Communication under Linux ttyUSBX

I'm using Linux Ubuntu and try to get serial communication to work.
Ok, what am I using...
I use a Raspberry Pi and connected it via USB/Serial-Adapter with an Inserial Measurement Unit (mulifunction sensor).
Just to clarify what i'm trying to do:
Establishing a connection betwenn Raspberry Pi and IMU.
To run IMU there are given steps i have to follow.
Power-on Sequence:
(a) power-on.
(b) Wait 800ms.
(c) Wait until NOT_READY bit goes to 0. NOT_READY is GLOB_CMD[3Eh]'s bit[10].
TXdata={0x3E,0x00,0x0d}. /* GLOB_CMD read command */
TXdata={0x3E,MSByte,LSByte,0x0d}. /* get response */
Confirm NOT_READY bit.
When NOT_READY becomes 0, it ends. Otherwise , please repeat (c).
(d) Confirm HARD_ERR bits. HARD_ERR is DIAG_STAT[3Ch]'s bit[6:5].
TXdata={0x3C,0x00,0x0d}. /* DIAG_STAT read command */
TXdata={0x3C,MSByte,LSByte,0x0d}. /* get response */
Confirm HARD_ERR is 00.
If HARD_ERR is 00, the IMU is OK. Otherwise, the IMU is faulty.
Register read and write:
[Read Example]
To read a 16bit-data from a register(addr=0x38).
TXdata={0x38,0x00,0x0d}. /* command */
RXdata={0x38,0x04,0x04,0x0d} /* response */
0x04 in 2nd byte of RXdata is Configuration mode.
0x04 in 3rd byte of RXdata is TAP=16.
Please note that read data unit is 16bit, and Most Significant Byte first.
-------------------------------------------------------------
[Write Example]
To write a 8bit-data into a register(addr=0x39).
TXdata={0xB9,0x01,0x0d}. /* command */
RXdata= w/o response
By sending this command, the IMU moves to Sampling mode.
Please note that write data unit is 8bit.
On my Linux Ubuntu there is a ttyUSB0 device given after connecting IMU.
So i tried to set Baudrate, Databits, Stopbits, Parity, flowcontrol.
First via stty-command, later with a simple c++-code.
I'm using this c++-code:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstdlib>
void SleepMs(int);
int main(int argc, char** argv)
{
int fd; // port file descriptor
char port[20] = "/dev/ttyUSB0"; // port to connect to
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // connect to port
if(fd == -1)
{
printf("Error while opening the port.\n");
return 1;
}
printf("Port opened successfully.\n");
fcntl(fd, F_SETOWN, getpid());
struct termios settings;
tcgetattr(fd, &settings);
settings.c_cflag &= ~(CBAUD | CSIZE | CREAD);
settings.c_cflag |= B230400;
settings.c_cflag |= CS8;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &settings); // apply the settings
int len = 7;
unsigned char bytes[len];
bytes[0] = 0x3E;
bytes[1] = 0x00;
bytes[2] = 0x0D;
bytes[3] = 0x3E;
bytes[4] = 0x00;
bytes[5] = 0x00;
bytes[6] = 0x0D;
int wr = write(fd, bytes, len);
unsigned char answer[32];
SleepMs(350);
int rd = -1;
int i;
while (rd==-1)
{
if(wr != 7)
{
printf("Error while sending!\n");
}
for(i=0; i<len; i++)
{
printf("%X sent\n", (unsigned int)bytes[i]);
SleepMs(350);
}
printf("\n");
printf("%d bytes sent.\n", wr);
printf("\n");
printf("Trying to read...\n");
printf("\n");
rd = read(fd, answer, 32);
SleepMs(350);
printf("%d\n", rd);
for(i=0; i<rd; i++)
{
printf("%X ", (unsigned int)answer[i]);
}
printf("\n\n");
}
close(fd);
return 0;
}
void SleepMs(int ms) {
usleep(ms*1000); //convert to microseconds
return;
}
If i start program, it tells me "Port open successfully" and writes given bytes in program.
But it receives no data.
I transmit 0x3E 0x00 0x0D to activate GLOB_CMD read command.
I have to confirm "Not Ready"-Bit is 0 but i dont get an answer with my serial connection.
So this is where i need your help, maybe someone got a hint for me.
How can i communicate with my IMU or via serialcommunication properly with Linux?
int wr = write(fd, bytes, len);
Your bytes array only needs to be 3 bytes long, so len should be 3. (The 0x3e is what the IMU should respond with, so it shouldn't be in your program except when checking the response.) When you read, you should only read the expected size of the answer (len=4). You don't need to sleep after writing, and probably not after reading.

kernel driver reading ok from user space, but writing back is always 0

So I'm working my way through kernel driver programming, and currently I'm trying to build a simple data transfer between application and kernel driver.
I am using simple character device as a link between these two, and I have succeeded to transfer data to driver, but I can't get meaningful data back to user space.
Kernel driver looks like this:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <asm/uaccess.h> /* copy_from/to_user */
MODULE_LICENSE("GPL");
//Declarations
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);
/* Structure that declares the usual file access functions */
struct file_operations memory_fops = {
read: memory_read,
write: memory_write,
open: memory_open,
release: memory_release
};
//Default functions
module_init(memory_init);
module_exit(memory_exit);
/* Global variables of the driver */
/* Major number */
int memory_major = 60;
/* Buffer to store data */
char* tx_buffer;
char* rx_buffer;
int BUFFER_SIZE=64;
int actual_rx_size=0;
int memory_init(void) {
int result;
/* Registering device */
result = register_chrdev(memory_major, "move_data", &memory_fops);
if (result < 0) {
printk(
"<1>move_data: cannot obtain major number %d\n", memory_major);
return result;
}
/* Allocating memory for the buffers */
//Allocate buffers
tx_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
rx_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
//Check allocation was ok
if (!tx_buffer || !rx_buffer) {
result = -ENOMEM;
goto fail;
}
//Reset the buffers
memset(tx_buffer,0, BUFFER_SIZE);
memset(rx_buffer,0, BUFFER_SIZE);
printk("<1>Inserting memory module\n");
return 0;
fail:
memory_exit();
return result;
}
void memory_exit(void) {
/* Freeing the major number */
unregister_chrdev(memory_major, "memory");
/* Freeing buffers */
if (tx_buffer) {
kfree(tx_buffer); //Note kfree
}
if (rx_buffer) {
kfree(rx_buffer); //Note kfree
}
printk("<1>Removing memory module\n");
}
//Read function
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {
printk("user requesting data, our buffer has (%d) \n", actual_rx_size);
/* Transfering data to user space */
int retval = copy_to_user(buf,rx_buffer,actual_rx_size);
printk("copy_to_user returned (%d)", retval);
return retval;
}
ssize_t memory_write( struct file *filp, char *buf,
size_t count, loff_t *f_pos) {
//zero the input buffer
memset(tx_buffer,0,BUFFER_SIZE);
memset(rx_buffer,0,BUFFER_SIZE);
printk("New message from userspace - count:%d\n",count);
int retval = copy_from_user(tx_buffer,buf,count);
printk("copy_from_user returned (%d) we read [%s]\n",retval , tx_buffer);
printk("initialize rx buffer..\n");
memcpy(rx_buffer,tx_buffer, count);
printk("content of rx buffer [%s]\n", rx_buffer);
actual_rx_size = count;
return count; //inform that we read all (fixme?)
}
//Always successfull
int memory_open(struct inode *inode, struct file *filp) { return 0; }
int memory_release(struct inode *inode, struct file *filp) { return 0; }
And the userspace application is simple as well:
#include <unistd.h> //open, close | always first, defines compliance
#include <fcntl.h> //O_RDONLY
#include <stdio.h>
#include <stdlib.h> //printf
#include <string.h>
int main(int args, char *argv[])
{
int BUFFER_SIZE = 20;
char internal_buf[BUFFER_SIZE];
int to_read = 0;
memset(internal_buf,0,BUFFER_SIZE);
if (args < 3) {
printf("2 Input arguments needed\nTo read 10 bytes: \"%s read 10\" \
\nTo write string \"hello\": \"%s write hello\"\nExiting..\n", argv[0], argv[0]);
return 1;
}
//Check the operation
if (strcmp(argv[1],"write") == 0) {
printf("input lenght:%d", strlen(argv[2]));
//Make sure our write fits to the internal buffer
if(strlen(argv[2]) >= BUFFER_SIZE) {
printf("too long input string, max buffer[%d]\nExiting..", BUFFER_SIZE);
return 2;
}
printf("write op\n");
memcpy(internal_buf,argv[2], strlen(argv[2]));
printf("Writing [%s]\n", internal_buf);
FILE * filepointer;
filepointer = fopen("/dev/move_data", "w");
fwrite(internal_buf, sizeof(char) , strlen(argv[2]), filepointer);
fclose(filepointer);
} else if (strcmp(argv[1],"read") == 0) {
printf("read op\n");
to_read = atoi(argv[2]);
FILE * filepointer;
filepointer = fopen("/dev/move_data", "r");
int retval = fread(internal_buf, sizeof(char) , to_read, filepointer);
fclose(filepointer);
printf("Read %d bytes from driver string[%s]\n", retval, internal_buf);
} else {
printf("first argument has to be 'read' or 'write'\nExiting..\n");
return 1;
}
return 0;
}
When I execute my application, this is what happens:
./rw write "testing testing"
kernel side:
[ 2696.607586] New message from userspace - count:15
[ 2696.607591] copy_from_user returned (0) we read [testing testing]
[ 2696.607593] initialize rx buffer..
[ 2696.607594] content of rx buffer [testing testing]
So all look correct. But when I try to read:
./rw read 15
read op
Read 0 bytes from driver string[]
Kernel
[ 617.096521] user requesting data, our buffer has (15)
[ 575.797668] copy_to_user returned (0)
[ 617.096528] copy_to_user returned (0)
I guess it's quite simple what I'm doing wrong, since if I don't return 0, I can get some data back, but for example if I read with cat, it will continue looping endlessly.
I would like to understand what mistakes I have made in my thinking.
Is there a way that kernel driver would just spit out it's buffer, and then return 0, so that I wouldn't have to build some protocol there in between to take care of how much data has been read etc.
Thanks for your suggestions!
Edit: corrected the printk statement in memory_write function, and added memory_read function trace
Your read function always returns 0 because you are returning retval, and not the count of bytes read. As long as the copy_to_user() call always succeeds, retval will always be 0. Instead, as long as copy_to_user() succeeds, you should return the number of bytes actually written to user space. This documentation states that copy_to_user() returns the total number of bytes that it was unable to copy.
As an aside, you are ignoring the value of count. It is very possible that the user is requesting less data than you have available in your buffer. You should never ignore count.
Now you have the problem where your function never returns a 0. Returning a 0 is important because is tells the user application that there is no more data available for reading and the user application should close the device file.
You need to keep track in your driver how many bytes have been read vs. how many bytes have been written. This may be implemented using your actual_rx_size.
Try this:
//Read function
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {
ssize_t bytes;
if (actual_rx_size < count)
bytes = actual_rx_size;
else
bytes = count;
printk("user requesting data, our buffer has (%d) \n", actual_rx_size);
/* Check to see if there is data to transfer */
if (bytes == 0)
return 0;
/* Transfering data to user space */
int retval = copy_to_user(buf,rx_buffer,bytes);
if (retval) {
printk("copy_to_user() could not copy %d bytes.\n", retval);
return -EFAULT;
} else {
printk("copy_to_user() succeeded!\n");
actual_rx_size -= bytes;
return bytes;
}
}

I2C_SLAVE ioctl purpose

I am writing code for implementing a simple i2c read/write function using the general linux i2c driver linux/i2c-dev.h
I am confused about the ioctl : I2C_SLAVE
The kernel documentation states as follows :
You can do plain i2c transactions by using read(2) and write(2) calls.
You do not need to pass the address byte; instead, set it through
ioctl I2C_SLAVE before you try to access the device
However I am using the ioctl I2C_RDWR where I again set the slave address using i2c_msg.addr.
The kernel documentation also mentions the following :
Some ioctl() calls are for administrative tasks and are handled by
i2c-dev directly. Examples include I2C_SLAVE
So is it must to use the ioctl I2C_SLAVE? If so do I need to set it just once or every time I perform a read and write?
If I had an i2c device I could have just tested the code on the device and would not have bothered you guys but unfortunately I don't have one right now.
Thanks for the help.
There are three major methods of communicating with i2c devices from userspace.
1. IOCTL I2C_RDWR
This method allows for simultaneous read/write and sending an uninterrupted sequence of message. Not all i2c devices support this method.
Before performing i/o with this method, you should check whether the device supports this method using an ioctl I2C_FUNCS operation.
Using this method, you do not need to perform an ioctl I2C_SLAVE operation -- it is done behind the scenes using the information embedded in the messages.
2. IOCTL SMBUS
This method of i/o is more powerful but the resulting code is more verbose. This method can be used if the device does not support the I2C_RDWR method.
Using this method, you do need to perform an ioctl I2C_SLAVE operation (or, if the device is busy, an I2C_SLAVE_FORCE operation).
3. SYSFS I/O
This method uses the basic file i/o system calls read() and write(). Uninterrupted sequential operations are not possible using this method. This method can be used if the device does not support the I2C_RDWR method.
Using this method, you do need to perform an ioctl I2C_SLAVE operation (or, if the device is busy, an I2C_SLAVE_FORCE operation).
I can't think of any situation when this method would be preferable to others, unless you need the chip to be treated like a file.
Full IOCTL Example
I haven't tested this example, but it shows the conceptual flow of writing to an i2c device.-- automatically detecting whether to use the ioctl I2C_RDWR or smbus technique.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#define I2C_ADAPTER "/dev/i2c-0"
#define I2C_DEVICE 0x00
int i2c_ioctl_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
// the extra byte is for the regaddr
size_t buff_size = 1 + size;
buf = malloc(buff_size);
if (buf == NULL) {
return -ENOMEM;
}
buf[j ++] = regaddr;
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_msg messages[] = {
{
.addr = dev,
.buf = buf,
.len = buff_size,
},
};
struct i2c_rdwr_ioctl_data payload = {
.msgs = messages,
.nmsgs = sizeof(messages) / sizeof(messages[0]),
};
ret = ioctl(fd, I2C_RDWR, &payload);
if (ret < 0) {
ret = -errno;
}
free (buf);
return ret;
}
int i2c_ioctl_smbus_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
buf = malloc(size);
if (buf == NULL) {
return -ENOMEM;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_smbus_ioctl_data payload = {
.read_write = I2C_SMBUS_WRITE,
.size = I2C_SMBUS_WORD_DATA,
.command = regaddr,
.data = (void *) buf,
};
ret = ioctl (fd, I2C_SLAVE_FORCE, dev);
if (ret < 0)
{
ret = -errno;
goto exit;
}
ret = ioctl (fd, I2C_SMBUS, &payload);
if (ret < 0)
{
ret = -errno;
goto exit;
}
exit:
free(buf);
return ret;
}
int i2c_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
unsigned long funcs;
if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
return -errno;
}
if (funcs & I2C_FUNC_I2C) {
return i2c_ioctl_write (fd, dev, regaddr, data, size);
} else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
return i2c_ioctl_smbus_write (fd, dev, regaddr, data, size);
} else {
return -ENOSYS;
}
}
int parse_args (uint8_t *regaddr, uint16_t *data, size_t size, char *argv[])
{
char *endptr;
int i;
*regaddr = (uint8_t) strtol(argv[1], &endptr, 0);
if (errno || endptr == argv[1]) {
return -1;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
data[i] = (uint16_t) strtol(argv[i + 2], &endptr, 0);
if (errno || endptr == argv[i + 2]) {
return -1;
}
}
return 0;
}
void usage (int argc, char *argv[])
{
fprintf(stderr, "Usage: %s regaddr data [data]*\n", argv[0]);
fprintf(stderr, " regaddr The 8-bit register address to write to.\n");
fprintf(stderr, " data The 16-bit data to be written.\n");
exit(-1);
}
int main (int argc, char *argv[])
{
uint8_t regaddr;
uint16_t *data;
size_t size;
int fd;
int ret = 0;
if (argc < 3) {
usage(argc, argv);
}
size = (argc - 2) * sizeof(uint16_t);
data = malloc(size);
if (data == NULL) {
fprintf (stderr, "%s.\n", strerror(ENOMEM));
return -ENOMEM;
}
if (parse_args(&regaddr, data, size, argv) != 0) {
free(data);
usage(argc, argv);
}
fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK);
ret = i2c_write(fd, I2C_DEVICE, regaddr, data);
close(fd);
if (ret) {
fprintf (stderr, "%s.\n", strerror(-ret));
}
free(data);
return ret;
}
If you use the read() and write() methods, calling ioctl with I2C_SLAVE once is enough. You can also use I2C_SLAVE_FORCE if the device is already in use.
However I haven't yet found a consistent way to read specific registers for every device using the read()/write() methods.
I'm not too sure if this helps because I don't use ioctl I2C_RDWR but I've been using the following code with success:
int fd;
fd = open("/dev/i2c-5", O_RDWR);
ioctl(fd, I2C_SLAVE_FORCE, 0x20);
i2c_smbus_write_word_data(fd, ___, ___);
i2c_smbus_read_word_data(fd, ___);
All I do is set I2C_SLAVE_FORCE once at the beginning and I can read and write as much as I want to after that.
PS - This is just a code sample and obviously you should check the returns of all of these functions. I'm using this code to communicate with a digital I/O chip. The two i2c_* functions are just wrappers that call ioctl(fd, I2C_SMBUS, &args); where args is a struct i2c_smbus_ioctl_data type.
For the interested, SLAVE_FORCE is used when the device in question is already being managed by a kernel driver. (i2cdetect will show UU for that address)

How might I learn to write char device drivers for Linux?

How to write char device drivers in Linux?
A very good example is the Linux "softdog", or software watchdog timer. When loaded, it will watch a special device for writes and take action depending on the frequency of those writes.
It also shows you how to implement a rudamentary ioctl interface, which is very useful.
The file to look at is drivers/watchdog/softdog.c
If you learn by example, that is a very good one to start with. The basic character devices (null, random, etc) as others suggest are also good, but do not adequately demonstrate how you need to implement an ioctl() interface.
A side note, I believe the driver was written by Alan Cox. If your going to learn from example, its never a bad idea to study the work of a top level maintainer. You can be pretty sure that the driver also illustrates adhering to proper Linux standards.
As far as drivers go (in Linux), character drivers are the easiest to write and also the most rewarding, as you can see your code working very quickly. Good luck and happy hacking.
Read this book: Linux Device Drivers published by O'Reilly.
Helped me a lot.
My favorite book for learning how the kernel works, BY FAR (and I've read most of them) is:
Linux Kernel Development (2nd Edition)
This book is fairly short, read it first, then read the O'Reilly book on drivers.
Read linux device driver 3rd edition. And the good thing is start coding. I am just pasting a simple char driver so that you can start coding.
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h> /*this is the file structure, file open read close */
#include<linux/cdev.h> /* this is for character device, makes cdev avilable*/
#include<linux/semaphore.h> /* this is for the semaphore*/
#include<linux/uaccess.h> /*this is for copy_user vice vers*/
int chardev_init(void);
void chardev_exit(void);
static int device_open(struct inode *, struct file *);
static int device_close(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static loff_t device_lseek(struct file *file, loff_t offset, int orig);
/*new code*/
#define BUFFER_SIZE 1024
static char device_buffer[BUFFER_SIZE];
struct semaphore sem;
struct cdev *mcdev; /*this is the name of my char driver that i will be registering*/
int major_number; /* will store the major number extracted by dev_t*/
int ret; /*used to return values*/
dev_t dev_num; /*will hold the major number that the kernel gives*/
#define DEVICENAME "megharajchard"
/*inode reffers to the actual file on disk*/
static int device_open(struct inode *inode, struct file *filp) {
if(down_interruptible(&sem) != 0) {
printk(KERN_ALERT "megharajchard : the device has been opened by some other device, unable to open lock\n");
return -1;
}
//buff_rptr = buff_wptr = device_buffer;
printk(KERN_INFO "megharajchard : device opened succesfully\n");
return 0;
}
static ssize_t device_read(struct file *fp, char *buff, size_t length, loff_t *ppos) {
int maxbytes; /*maximum bytes that can be read from ppos to BUFFER_SIZE*/
int bytes_to_read; /* gives the number of bytes to read*/
int bytes_read;/*number of bytes actually read*/
maxbytes = BUFFER_SIZE - *ppos;
if(maxbytes > length)
bytes_to_read = length;
else
bytes_to_read = maxbytes;
if(bytes_to_read == 0)
printk(KERN_INFO "megharajchard : Reached the end of the device\n");
bytes_read = bytes_to_read - copy_to_user(buff, device_buffer + *ppos, bytes_to_read);
printk(KERN_INFO "megharajchard : device has been read %d\n",bytes_read);
*ppos += bytes_read;
printk(KERN_INFO "megharajchard : device has been read\n");
return bytes_read;
}
static ssize_t device_write(struct file *fp, const char *buff, size_t length, loff_t *ppos) {
int maxbytes; /*maximum bytes that can be read from ppos to BUFFER_SIZE*/
int bytes_to_write; /* gives the number of bytes to write*/
int bytes_writen;/*number of bytes actually writen*/
maxbytes = BUFFER_SIZE - *ppos;
if(maxbytes > length)
bytes_to_write = length;
else
bytes_to_write = maxbytes;
bytes_writen = bytes_to_write - copy_from_user(device_buffer + *ppos, buff, bytes_to_write);
printk(KERN_INFO "megharajchard : device has been written %d\n",bytes_writen);
*ppos += bytes_writen;
printk(KERN_INFO "megharajchard : device has been written\n");
return bytes_writen;
}
static loff_t device_lseek(struct file *file, loff_t offset, int orig) {
loff_t new_pos = 0;
printk(KERN_INFO "megharajchard : lseek function in work\n");
switch(orig) {
case 0 : /*seek set*/
new_pos = offset;
break;
case 1 : /*seek cur*/
new_pos = file->f_pos + offset;
break;
case 2 : /*seek end*/
new_pos = BUFFER_SIZE - offset;
break;
}
if(new_pos > BUFFER_SIZE)
new_pos = BUFFER_SIZE;
if(new_pos < 0)
new_pos = 0;
file->f_pos = new_pos;
return new_pos;
}
static int device_close(struct inode *inode, struct file *filp) {
up(&sem);
printk(KERN_INFO "megharajchard : device has been closed\n");
return ret;
}
struct file_operations fops = { /* these are the file operations provided by our driver */
.owner = THIS_MODULE, /*prevents unloading when operations are in use*/
.open = device_open, /*to open the device*/
.write = device_write, /*to write to the device*/
.read = device_read, /*to read the device*/
.release = device_close, /*to close the device*/
.llseek = device_lseek
};
int chardev_init(void)
{
/* we will get the major number dynamically this is recommended please read ldd3*/
ret = alloc_chrdev_region(&dev_num,0,1,DEVICENAME);
if(ret < 0) {
printk(KERN_ALERT " megharajchard : failed to allocate major number\n");
return ret;
}
else
printk(KERN_INFO " megharajchard : mjor number allocated succesful\n");
major_number = MAJOR(dev_num);
printk(KERN_INFO "megharajchard : major number of our device is %d\n",major_number);
printk(KERN_INFO "megharajchard : to use mknod /dev/%s c %d 0\n",DEVICENAME,major_number);
mcdev = cdev_alloc(); /*create, allocate and initialize our cdev structure*/
mcdev->ops = &fops; /*fops stand for our file operations*/
mcdev->owner = THIS_MODULE;
/*we have created and initialized our cdev structure now we need to add it to the kernel*/
ret = cdev_add(mcdev,dev_num,1);
if(ret < 0) {
printk(KERN_ALERT "megharajchard : device adding to the kerknel failed\n");
return ret;
}
else
printk(KERN_INFO "megharajchard : device additin to the kernel succesful\n");
sema_init(&sem,1); /* initial value to one*/
return 0;
}
void chardev_exit(void)
{
cdev_del(mcdev); /*removing the structure that we added previously*/
printk(KERN_INFO " megharajchard : removed the mcdev from kernel\n");
unregister_chrdev_region(dev_num,1);
printk(KERN_INFO "megharajchard : unregistered the device numbers\n");
printk(KERN_ALERT " megharajchard : character driver is exiting\n");
}
MODULE_AUTHOR("A G MEGHARAJ(agmegharaj#gmail.com)");
MODULE_DESCRIPTION("A BASIC CHAR DRIVER");
//MODULE_LICENCE("GPL");
module_init(chardev_init);
module_exit(chardev_exit);
and this is the make file.
obj-m := megharajchard.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
load script. make sure the major number is 251 or else change it accordingly.
#!/bin/sh
sudo insmod megharajchard.ko
sudo mknod /dev/megharajchard c 251 0
sudo chmod 777 /dev/megharajchard
unload script,
#!/bin/sh
sudo rmmod megharajchard
sudo rm /dev/megharajchard
also a c program to test the operation of your device
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<malloc.h>
#define DEVICE "/dev/megharajchard"
//#define DEVICE "megharaj.txt"
int debug = 1, fd = 0;
int write_device() {
int write_length = 0;
ssize_t ret;
char *data = (char *)malloc(1024 * sizeof(char));
printf("please enter the data to write into device\n");
scanf(" %[^\n]",data); /* a space added after"so that it reads white space, %[^\n] is addeed so that it takes input until new line*/
write_length = strlen(data);
if(debug)printf("the length of dat written = %d\n",write_length);
ret = write(fd, data, write_length);
if(ret == -1)
printf("writting failed\n");
else
printf("writting success\n");
if(debug)fflush(stdout);/*not to miss any log*/
free(data);
return 0;
}
int read_device() {
int read_length = 0;
ssize_t ret;
char *data = (char *)malloc(1024 * sizeof(char));
printf("enter the length of the buffer to read\n");
scanf("%d",&read_length);
if(debug)printf("the read length selected is %d\n",read_length);
memset(data,0,sizeof(data));
data[0] = '0\';
ret = read(fd,data,read_length);
printf("DEVICE_READ : %s\n",data);
if(ret == -1)
printf("reading failed\n");
else
printf("reading success\n");
if(debug)fflush(stdout);/*not to miss any log*/
free(data);
return 0;
}
int lseek_device() {
int lseek_offset = 0,seek_value = 0;
int counter = 0; /* to check if function called multiple times or loop*/
counter++;
printf("counter value = %d\n",counter);
printf("enter the seek offset\n");
scanf("%d",&lseek_offset);
if(debug) printf("seek_offset selected is %d\n",lseek_offset);
printf("1 for SEEK_SET, 2 for SEEK_CUR and 3 for SEEK_END\n");
scanf("%d", &seek_value);
printf("seek value = %d\n", seek_value);
switch(seek_value) {
case 1: lseek(fd,lseek_offset,SEEK_SET);
return 0;
break;
case 2: lseek(fd,lseek_offset,SEEK_CUR);
return 0;
break;
case 3: lseek(fd,lseek_offset,SEEK_END);
return 0;
break;
default : printf("unknown option selected, please enter right one\n");
break;
}
/*if(seek_value == 1) {
printf("seek value = %d\n", seek_value);
lseek(fd,lseek_offset,SEEK_SET);
return 0;
}
if(seek_value == 2) {
lseek(fd,lseek_offset,SEEK_CUR);
return 0;
}
if(seek_value == 3) {
lseek(fd,lseek_offset,SEEK_END);
return 0;
}*/
if(debug)fflush(stdout);/*not to miss any log*/
return 0;
}
int lseek_write() {
lseek_device();
write_device();
return 0;
}
int lseek_read() {
lseek_device();
read_device();
return 0;
}
int main()
{
int value = 0;
if(access(DEVICE, F_OK) == -1) {
printf("module %s not loaded\n",DEVICE);
return 0;
}
else
printf("module %s loaded, will be used\n",DEVICE);
while(1) {
printf("\t\tplease enter 1 to write\n \
2 to read\n \
3 to lseek and write\
4 to lseek and read\n");
scanf("%d",&value);
switch(value) {
case 1 :printf("write option selected\n");
fd = open(DEVICE, O_RDWR);
write_device();
close(fd); /*closing the device*/
break;
case 2 :printf("read option selected\n");
/* dont know why but i am suppoesed to open it for writing and close it, i cant keep open and read.
its not working, need to sort out why is that so */
fd = open(DEVICE, O_RDWR);
read_device();
close(fd); /*closing the device*/
break;
case 3 :printf("lseek option selected\n");
fd = open(DEVICE, O_RDWR);
lseek_write();
close(fd); /*closing the device*/
break;
case 4 :printf("lseek option selected\n");
fd = open(DEVICE, O_RDWR);
lseek_read();
close(fd); /*closing the device*/
break;
default : printf("unknown option selected, please enter right one\n");
break;
}
}
return 0;
}
Have a look at some of the really simple standard ones - "null", "zero", "mem", "random", etc, in the standard kernel. They show the simple implementation.
Obviously if you're driving real hardware it's more complicated- you need to understand how to interface with the hardware as well as the subsystem APIs (PCI, USB etc) for your device. You might need to understand how to use interrupts, kernel timers etc as well.
Just check the character driver skeleton from here http://www.linuxforu.com/2011/02/linux-character-drivers/....Go ahead and read all the topics here, understand thoroughly.(this is kinda a tutorial-so play along as said).
See how "copy_to_user" and "copy_from_user" functions work, which you can use in read/write part of the driver function callbacks.
Once you are done with this, start reading a basic "tty" driver.
Focus, more on the driver registration architecture first, which means:-
See what structures are to be filled- ex:- struct file_operations f_ops = ....
Which are the function responsible to register a particular structure with core . ex:- _register_driver.
Once you are done with the above, see what functionality you want with the driver(policy), then think of way to implement that policy(called mechanism)- the policy and mechanism allows you to distinguish between various aspects of the driver.
write compilation makefiles(its hard if you have multiple files- but not tht hard).
Try to resolve the errors and warnings,
and you will be through.
When writing mechanism, never forget what it offers to the applications in user space.

Resources