why does ioctl return "bad address" - c

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!

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.

Related

Failed to read pci csr via mmap using uint64_t pointer [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last month.
Improve this question
I'm trying to reading PCI CSR (Configuration Space Register) on my system via open,mmap /dev/mem.
I met some problems when using 8 byte length reading
Here is the minimal working example of my code
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FATAL \
do { \
fprintf(stderr, \
"Error at line %d, file %s (%d) [%s]\n", \
__LINE__, \
__FILE__, \
errno, \
strerror(errno)); \
exit(1); \
} while(0)
#define PAGE_SIZE 4096UL
#define PAGE_MASK (PAGE_SIZE - 1)
typedef struct rw_config rw_config;
struct rw_config {
uint64_t address;
uint64_t data;
};
static uint64_t _mmio_read_worker(uint64_t address) {
int fd;
void *map_base = NULL;
void *map_address = NULL;
uint64_t result = 0UL;
if((fd = open("/dev/mem", O_RDONLY | O_SYNC)) < 0) FATAL;
// PAGE_SIZE = 4096UL
// PAGE_MASK = (PAGE_SIZE - 1) = 4095UL
if((map_base = mmap(NULL,
PAGE_SIZE,
PROT_READ,
MAP_SHARED,
fd,
(address & ~PAGE_MASK)))
== MAP_FAILED)
FATAL;
map_address = map_base + (address & PAGE_MASK);
result = *(uint64_t *)map_address;
printf("uint32_t 0x%016x, uint64_t 0x%016lx\n",
(*(uint32_t *)map_address),
(*(uint64_t *)map_address));
close(fd);
return result;
}
void rw_worker(rw_config *cfg) {
cfg->data = _mmio_read_worker(cfg->address);
return;
}
int main(int argc, char *argv[]) {
rw_config *cfg = malloc(sizeof(rw_config));
cfg->address = 0x80000000;
cfg->data = 0x0;
rw_worker(cfg);
return 0;
}
Reading the address = 0x80000000 which is pci mmio base address.
The output of my code is as follows:
uint32_t 0x0000000009a28086, uint64_t 0xffffffffffffffff
And I try to using gdb to get some information.
(gdb) printf "0x%llx\n",(*(uint64_t *)map_address)
0x10000009a28086
# before assigning 'result'
(gdb) printf "0x%llx\n",result
0x0
(gdb) next
# after assigning 'result'
(gdb) printf "0x%llx\n",result
0xffffffffffffffff
(gdb) print map_address
$2 = (void *) 0x7ffff7ffb000
(gdb) x/1xg 0x7ffff7ffb000
0x7ffff7ffb000: 0x0010000009a28086
I guess I fail to casting (void*) to *(uint64_t *), but why?
The value storage in map_address is correct, am I using the wrong way to get the value?
After reading the replies from other members, I read some documents that may be related to this bug, and the following are some of my insights:
I tried testing with another address which NOT in PCI CSR(Configuration Space Register), and got the correct value. So I think this bug is related to hardware registers rather than software implementation
In EDK II UEFI Writer Guide link, using 64bits read on PCI BAR(Base Address Register, which is a part of PCI CSR) may cause an alignment fault, you should use 2x of 32bits read to achieve 64bits read. Although in the example it is not enforced that the whole CSR has this limitation, but I think there is already a good reason for this bug.
PCIe config space must be read using 1, 2, or 4-byte accesses.
8-byte accesses are not permitted.
This is specified in the PCIe spec, section 2.2.7.1: "For Configuration Requests, Length[9:0] must be 00 0000 01b." (Length is specified in DW.)
My experience is that 8-byte accesses always return FF in all bytes.

mmap file returns a pointer to an inaccessible place in memory

I have this program that is supposed to mmap a file in read-write mode and be able to edit its contents. Also the file this is written for is about 40-50 GB, so I need mmap64. The problem is, while mmap64 does not return an error, the address it returns is not accessible.
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
typedef unsigned long long u64;
void access_test(u64 p, u64 sz)
{
u64 i;
char tmp;
for (i=0; i<sz; i++) {
tmp = *(char*)(p+i);
}
}
int main(int argc, char *argv[])
{
int fd;
long long int sz, p;
struct stat buf;
fd = open(argv[1], O_RDWR, 0x0666);
if (fd == -1) {
perror("open");
return 1;
}
fstat64(fd, &buf);
sz = buf.st_size;
printf("File size: 0x%016llx\n", sz);
p = mmap64 (0, buf.st_size, PROT_READ | PROT_WRITE , MAP_SHARED, fd, 0);
if (p == -1) {
perror ("mmap");
return 1;
}
access_test(p,sz);
if (close (fd) == -1) {
perror ("close");
return 1;
}
if (munmap ((void*)p, buf.st_size) == -1) {
perror ("munmap");
return 1;
}
return 0;
}
The result of this is on a small file:
$ ./testmmap minicom.log
File size: 0x0000000000000023
[1] 8282 segmentation fault (core dumped) ./testmmap minicom.log
The same goes for the big one.
Always enable warnings when you compile
Here is the result with warnings enabled:
$ gcc mmp.c -Wall -g
mmp.c: In function ‘access_test’:
mmp.c:18:10: warning: variable ‘tmp’ set but not used [-Wunused-but-set-variable]
char tmp;
^
mmp.c: In function ‘main’:
mmp.c:36:5: warning: implicit declaration of function ‘fstat64’ [-Wimplicit-function-declaration]
fstat64(fd, &buf);
^
mmp.c:40:5: warning: implicit declaration of function ‘mmap64’ [-Wimplicit-function-declaration]
p = mmap64 (0, buf.st_size, PROT_READ | PROT_WRITE , MAP_SHARED, fd, 0);
The last two warnings here are extremely important. They say there is no prototype for mmap64. C therefore gives you a default prototype, and it is wrong, at least for the mmap64() call (since the prototype will return an int, which cannot represent a pointer on a 64-bit Linux host)
The argument to fstat64() is a struct stat64 too BTW, which is another issue.
Make the specific 64-bit functions available
If you want to make the fstat64()/mmap64() function available, you need to compile the code with the _LARGEFILE and LARGEFILE64_SOURCE #define, see information here, so you should compile this as e.g:
gcc -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE mmp.c -Wall -g
Or use #define _FILE_OFFSET_BITS=64
There is however no need to do this. Just call the normal fstat() and mmap() and #define _FILE_OFFSET_BITS=64 when compiling. e.g.:
gcc -D_FILE_OFFSET_BITS=64 mmp.c -Wall -g
This will enable support for large files, and e.g. translate the mmap() call to mmap64() if it is needed (e.g. if you're on a 32-bit host).
If you are trying to mmap() an 50 GB file, you anyway need to be on a 64-bit host, and on a 64-bit Linux host there's no need for any of this - mmap() and fstat() handles large files without any need to do anything.
Use pointers
The next issue is you're assigning the return value of mmap() to an integer. This might happen to work, but the code does look odd because of it. If you want to treat the thing as a char *, assign it to a char *. Don't play tricks with casting pointers around to a 64-bit integer type.
E.g. your access function should be:
void access_test(char *p, u64 sz)
{
u64 i;
char tmp;
for (i=0; i<sz; i++) {
tmp = p[i];
}
}
And p should be declared as char *p; in main(), or use uint8_t *p; if you intend to treat the data as binary data.

write() returns -1 when writing to I2C_SLAVE device

I've read through the Linux kernel documents on i2c and written a code to try to replicate the command i2cset -y 0 0x60 0x05 0xff
The code that I've written is here:
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <string.h>
int main(){
int file;
file = open("/dev/i2c-0", O_RDWR);
if (file < 0) {
exit(1);
}
int addr = 0x60;
if(ioctl(file, I2C_SLAVE, addr) < 0){
exit(1);
}
__u8 reg = 0x05;
__u8 res;
__u8 data = 0xff;
int written = write(file, &reg, 1);
printf("write returned %d\n", written);
written = write(file, &data, 1);
printf("write returned %d\n", written);
}
When I compile and run this code I get:
write returned -1
write returned -1
I've tried to follow exactly what the docs tell me, my understanding is that the address is set first with the call to ioctl, then I need to write() the register and then the data that I want sent to the register.
I've also tried to use use SMbus, but I can't get my code to compile using this, it complains at the linking stage that it can't find the functions.
Have I made any mistakes in this code? I'm a beginner to i2c and don't have a lot of experience with c either.
EDIT: errno give the following message: Operation not supported. I am logged in as root on this machine though, so I don't think it can be a permissions thing, although I may be wrong.
The way I got around this problem was to use SMBus, in particular the functions i2c_smbus_write_byte_data and i2c_smbus_read_byte_data. I was able to use these functions to successfully read and write to the device.
I did have a little trouble finding these functions, I kept trying to download libraries using apt-get to install the appropriate header files. In the end I simply downloaded the files smbus.c and smbus.h.
Then the code I needed was:
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include "smbus.h"
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
int main(){
int file;
file = open("/dev/i2c-0", O_RDWR);
if (file < 0) {
exit(1);
}
int addr = 0x60;
if(ioctl(file, I2C_SLAVE, addr) < 0){
exit(1);
}
__u8 reg = 0x05; /* Device register to access */
__s32 res;
res = i2c_smbus_write_byte_data(file, reg, 0xff);
close(file);
}
Then if I compile the smbus.c file: gcc -c smbus.c and myfile: gcc -c myfile.c, then link them: gcc smbus.o myfile.o -o myexe I get a working executable that runs my I2C command. Ofcourse, I have smbus.c and smbus.h in the same directory as myfile.c.
In C, you can check the content of the errno variable to get more details into what went wrong. It is automatically declared when including errno.h and you can get a more descriptive text by calling strerror(errno).
Have you checked that you had write access to /dev/i2c-0 ?

Avahi dns_sd compatibility layer fails to run Browse callback

Background
I'm working on a cross-platform Zeroconf/Bonjour/DNS-SD library for Haskell, and figured my best bet would bet would be to target the dns_sd.h API. Under Linux, the implementation of this interface is provided by Avahi, which claims to support a subset of the Bonjour API.
Problem
As a sanity check for my library, I've written a small test program in C that just uses the bare bones of the API. It browses for any service on the network of type _http._tcp, prints a message as soon as it sees one, and then dies:
#include <dns_sd.h>
#include <stdio.h>
#include <stdlib.h>
void cb(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context) {
printf("called!\n");
}
int main() {
DNSServiceRef sd = malloc(sizeof(DNSServiceRef));
const char *regtype = "_http._tcp";
DNSServiceErrorType err1 = DNSServiceBrowse(&sd, 0, 0, regtype, NULL, &cb, NULL);
printf("err1=%d\n", err1);
DNSServiceErrorType err2 = DNSServiceProcessResult(sd);
printf("err2=%d\n", err2);
return 0;
}
On my Mac, this test program works fine in both C and the equivalent Haskell (it finds my printer; exciting!):
$ gcc test.c -o test
$ ./test
err1=0
called!
err2=0
But on my Linux machine, the program berates me before exiting without invoking the callback:
$ gcc test.c -o test -ldns_sd
$ ./test
*** WARNING *** The program 'test' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=test>
err1=0
err2=0
Questions
Is the Avahi dns_sd compatibility layer still a suitable target for a cross-platform binding? Or is that warning message serious enough about using the native Avahi API that I should consider retargeting?
What is the state of the art for cross-platform Zeroconf in C?
By reason unknown to me, it works only with non-blocking calls. Below is the improved code. Socket from Avahi is set to a non-blocking mode and then select (3) is used to wait for available data. DNSServiceProcessResult(sd) has to be called each time there is data on the socket so it may have been pure luck that your example worked on other platforms.
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dns_sd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
static int set_nonblocking(int fd)
{
int flags;
/* If they have O_NONBLOCK, use the Posix way to do it */
#if defined(O_NONBLOCK)
/* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
flags = 0;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#else
/* Otherwise, use the old way of doing it */
flags = 1;
return ioctl(fd, FIOBIO, &flags);
#endif
}
void cb(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context) {
printf("called %s %s!\n", serviceName, regtype);
}
int main() {
DNSServiceRef sd = malloc(sizeof(DNSServiceRef));
const char *regtype = "_http._tcp";
DNSServiceErrorType err1 = DNSServiceBrowse(&sd, 0, 0, regtype, NULL, &cb, NULL);
printf("err1=%d\n", err1);
int socket = DNSServiceRefSockFD(sd);
set_nonblocking(socket);
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(socket, &read_fds);
while(1) {
if(select(socket+1, &read_fds, NULL, NULL, NULL) < 0) {
perror("select");
}
DNSServiceErrorType err2 = DNSServiceProcessResult(sd);
printf("err2=%d\n", err2);
if(err2 != 0)
return 2;
}
return 0;
}

Read structure contents using read() system call

#include "common.h"
#include <string.h>
struct buffer
{
int no;
char name[20];
};
int main()
{
struct buffer buf;
struct buffer read_buf;
int fd;
if((fd = open("read_write.txt",O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) < 0)
{
PRINT_ERROR(errorbuf);
}
buf.no = 10;
strcpy(buf.name,"nitin");
if(write(fd, &buf, sizeof(struct buffer)) < 0)
{
PRINT_ERROR(errorbuf);
}
printf("Written successfully\n");
/* Add code here to read the content of the structure into 'read_buf' */
exit(0);
}
common.h
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
char errorbuf[20];
#define PRINT_ERROR(errorbuf) \
do \
{ \
sprintf(errorbuf,"%s:%d",__FILE__,__LINE__); \
perror(errorbuf); \
exit(-1); \
}while(0);
I have written a structure into the file. But i am getting confused on how to retrieve each element of the structure written earlier into the object 'read_buf'. Kindly tell me how to do this.
Thanks
lseek(fd,0,SEEK_SET);
read(fd,&buf,sizeof(struct buffer);
Will work, but there are some other things you will have to worry about.
This is not portable.
You will have to worry about structure packing on different builds.
You will have endian problems cross platform.
Windows you may need O_BINARY.
It is nearly always better to repackage to structure in to a known format (with known endianess) so that you can reliably read the data back.
You read it back like this:
ssize_t bytes_read = read(fd, &buf, sizeof buf);
if(-1 == bytes_read)
; // handle read error
if(sizeof buf != bytes_read)
; // handle incomplete read
Writing/reading binary structures to/from files is not portable (depending on the platform architecture, structure padding etc). To make your program robust, you can use something like XDR.

Resources