why does ioctl return "bad address"

sven picture sven · Apr 2, 2013 · Viewed 16.2k times · Source

I use the the code below to output data from SPI port of an embedded board (olimex imx233-micro -- it is not a board specific question). When I run the code ioctl return "bad address". I am modifying the code on http://twilight.ponies.cz/spi-test.c which works fine. Could anyone tell me what am I doing wrong?

root@ubuntu:/home# gcc test.c -o test
test.c:20: warning: conflicting types for ‘msg_send’
test.c:16: note: previous implicit declaration of ‘msg_send’ was here
root@ubuntu:/home# ./test
errno:Bad address - cannot send SPI message
root@ubuntu:/home# uname -a
Linux ubuntu 3.7.1 #2 Sun Mar 17 03:49:39 CET 2013 armv5tejl GNU/Linux

Code:

//test.c
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>

static uint16_t delay;

int main(int argc,char *argv[]){
     msg_send(254); //the message that I want to send decimal "254"
     return 0;
}

void msg_send(int msg){
    int fd;
    int ret = 0;
    fd = open("/dev/spidev32766.1", O_RDWR); //ls /dev outputs spidev32766.1
    if(fd < 0){
        fprintf(stderr, "errno:%s - FD could be not opened\n ", strerror(errno));  
        exit(1);
        }

    struct spi_ioc_transfer tr = {
        .len = 1,
        .delay_usecs = delay,
        .speed_hz = 500000, //500 kHz
        .bits_per_word = 8,
        .tx_buf = msg,
        .rx_buf = 0, //half duplex
    };

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret <1 ){
        fprintf(stderr, "errno:%s - cannot send SPI message\n ", strerror(errno));
    }
    close(fd);
}

Thank you!

Answer

Adam Rosenfield picture Adam Rosenfield · Apr 2, 2013

The error message "Bad address" comes from the error code EFAULT, which happens when you pass an address to the kernel which is not a valid virtual address in your process's virtual address space. The address to your tr structure is clearly valid, so the problem must be with one of its members.

According to the definition of struct spi_ioc_transfer, the .tx_buf and .rx_buf members must be pointers to userspace buffers, or null. You're setting .tx_buf to the integer 254, which is not a valid userspace pointer, so that's where the bad address is coming from.

I'm not familiar with this IOCTL, so my best guess is that you need to bass the data in binary. One way to do that would be this:

struct spi_ioc_transfer tr = {
    .len = sizeof(msg),  // Length of rx and tx buffers
     ...
    .tx_buf = (u64)&msg, // Pointer to tx buffer
    ...
};

If you need to send it as ASCII instead, then you should use a function such as snprintf(3) to convert the integer to an ASCII string, and then point the TX buffer at that string and set the length accordingly.