Correct procedure and memory addresses to setup a virtio-net ethernet device on a sel4 microkernel - c

In short:
I am trying to run the sel4 microkernel inside a x86_64 virtual machine and can't get the ethernet interface working.
What is the correct procedure to get internet connectivity (via a vitio-net ethernet device) on a sel4 microkernel? And what are the correct (memory) addresses?
Long version:
I have tried the camkes (picoserver) examples with the e1000 netdevice but couldn't get them to work so I decided to learn some new things and start from scratch. Also I decided to use virtio-net(together with vhost) instead of an emulated e1000 device for better performance. My plan is to use ethif_virtio_pci_init to initialise a eth_driver struct and then pass the struct on to picoTCP. For now I can find the virtio PCI device in sel4 but I am unsure how to correctly access it and create the ethif_virtio_pci_config_t needed for ethif_virtio_pci_init.
Some information from libethdrivers virtio_pci.h:
typedef struct ethif_virtio_pci_config {
uint16_t io_base;
void *mmio_base;
} ethif_virtio_pci_config_t;
/**
* This function initialises the hardware and conforms to the ethif_driver_init
* type in raw.h
* #param[out] eth_driver Ethernet driver structure to fill out
* #param[in] io_ops A structure containing os specific data and
* functions.
* #param[in] config Pointer to a ethif_virtio_pci_config struct
*/
int ethif_virtio_pci_init(struct eth_driver *eth_driver, ps_io_ops_t io_ops, void *config);
so for the ethif_virtio_pci_config_t I need an uint16_t io_base address and a pointer to the MMIO base.
This is the information I have obtained so far:
Found virtio_net_pci device
BASE_ADDR[0] ----
base_addr_space[0]: 0x1 [PCI_BASE_ADDRESS_SPACE_IO]
base_addr_type[0]: 0x0 [ 32bit ]
base_addr_prefetchable[0]: no
base_addr[0]: 0xc000
base_addr_size_mask[0]: 0xffffffe0
BASE_ADDR[1] ----
base_addr_space[1]: 0x0 [PCI_BASE_ADDRESS_SPACE_MEMORY]
base_addr_type[1]: 0x0 [ 32bit ]
base_addr_prefetchable[1]: no
base_addr[1]: 0xfeb91000
base_addr_size_mask[1]: 0xfffff000
BASE_ADDR[2] ----
BASE_ADDR[3] ----
BASE_ADDR[4] ----
base_addr_space[4]: 0x0 [PCI_BASE_ADDRESS_SPACE_MEMORY]
base_addr_type[4]: 0x4 [ 64bit ]
base_addr_prefetchable[4]: yes
base_addr[4]: 0xfe000000
base_addr_size_mask[4]: 0xffffc000
BASE_ADDR[5] ----
As far as I understand I now need to map the pysical address to a virtual one. For that I created an IO-mapper but I am not sure what to map. The whole dma region starting at 0x8000000 or just the address of the virtio device? As far as I understand the new virtual address would be my MMIO base pointer but what is the uint16_t io_base than?
This is my code so far, the part I am unsure about is at the end:
#define ALLOCATOR_STATIC_POOL_SIZE ((1 << seL4_LargePageBits) * 10)
static simple_t simple;
static ps_io_mapper_t io_mapper;
static char allocator_mem_pool[ALLOCATOR_STATIC_POOL_SIZE];
static vka_t vka;
static vspace_t vspace;
static sel4utils_alloc_data_t data;
static ltimer_t timer;
int main() {
PRINT_DBG("Hello World\n");
seL4_BootInfo *info = platsupport_get_bootinfo();
simple_default_init_bootinfo(&simple, info);
/* print out bootinfo and other info about simple */
// simple_print(&simple);
allocman_t *allocman = bootstrap_use_current_simple(&simple, ALLOCATOR_STATIC_POOL_SIZE, allocator_mem_pool);
if (allocman == NULL) {
ZF_LOGF("Failed to create allocman");
}
allocman_make_vka(&vka, allocman);
int error = sel4utils_bootstrap_vspace_with_bootinfo_leaky(&vspace,
&data, simple_get_pd(&simple),
&vka, info);
if (error != 0) {
PRINT_DBG("Failed to create virtual memory manager. Error: %d\n", error);
return -1;
}
error = sel4platsupport_new_io_mapper(&vspace, &vka, &io_mapper);
if (error != 0) {
PRINT_DBG("Failed to create io mapper. Error: %d\n", error);
return -1;
}
ps_io_ops_t io_ops;
error = sel4platsupport_new_io_ops(&vspace, &vka, &simple, &io_ops);
if (error != 0) {
PRINT_DBG("Failed to create io ops. Error: %d\n", error);
return -1;
}
ps_io_port_ops_t port_ops;
int error = sel4platsupport_get_io_port_ops(&port_ops, &simple, &vka);
if (error != 0) {
PRINT_DBG("Failed to find io port ops. Error: %d\n", error);
return -1;
}
printf("Start scannning\n");
libpci_scan(port_ops);
PRINT_DBG("Found %u devices\n", libpci_num_devices);
for (uint32_t i = 0; i < libpci_num_devices; ++i) {
PRINT_DBG("PCI device %u. Vendor id: %x. Device id: %x\n",
i, libpci_device_list[i].vendor_id, libpci_device_list[i].device_id);
}
libpci_device_t* virtio_net_pci = libpci_find_device(0x1af4, 0x1000);
if (!virtio_net_pci) {
PRINT_DBG("Failed to find the virtio_net_pci device\n");
// return -1;
}else{
// libpci_device_iocfg_debug_print(&virtio_net_pci->cfg,true);
PRINT_DBG("Found virtio_net_pci device\n");
libpci_device_iocfg_debug_print(&virtio_net_pci->cfg,false);
}
//Now what?
unsigned long phys = 0x8000000; //what physical address to map?
void *mmio_ptr = ps_io_map(&io_mapper, phys, 4096, 0, PS_MEM_NORMAL);
memset(ptr, 0, 4096);
if (mmio_ptr == NULL) {
PRINT_DBG("Failed to map phys addr. Error: %p\n", ptr);
return -1;
}
ethif_virtio_pci_config_t me_config;
me_config.mmio_base = mmio_ptr; //is this correct?
//me_config.io_base = ?
I read alot about the sel4 kernel but I am still new to most of the concepts of the sel4 microkernel (and Linux kernel) so I am very grateful for any tipps and recommendations. I am normally working with embedded, microcontrollers and more "bare metal" platforms and wanted to learn something new but for now alot is very confusing.

Related

Memory leak synchronous reading interrupt transfer data via libUSB

I discovered a memory leak reading data via USB interrupt transfer using libUSB synchronously. My simple user program is not using any dynamic memory allocation itself. Internally libusb makes excessive use of dynamic memory allocation. The communication flow is working as expected. Is there a special function to free any internal dynamic memory after using libusb_interrupt_transfer? Does anyone have an idea what causes the continously increase of memory during runtime?
My protocol implements a two way handshake. Because of this a simple data exchange causes a OUT(request), IN(Ack/Nack), IN(Response) and OUT(Ack/Nack) transfer. The report size is 32 Bytes, the outEndpointAddr is 1, the inEndpointAddr is 129, Here are the relevant code snippets.
int main (void)
{
uint32_t devFound = 0;
uint32_t devErrors = 0;
...
int libUsbErr = 0;
if(!findSensor(&devFound, &devErrors, &libUsbErr, foundCB))
printf("finding sensor failed %d\n", libUsbErr);
if(!openSensor(mySensor, &libUsbErr))
printf("open sensor failed %d\n", libUsbErr);
int i = 0;
while(1)
{
printf("[%06d] Int Temp %f C\n",i++, readIntTemper());
Delay(0.5);
}
closeSensor(&mySensor, NULL);
closeSensorContext();
return 0;
}
float readIntTemper()
{
static uint8_t tmp[32];
static uint8_t response[32];
...//Prepare request frame
int libUsbErr = 0;
if(!HID_Write(mySensor, tmp, &written, 4000, &libUsbErr))
{
printf("write request failed %d\n", libUsbErr);
return 0;
}
//Read Ack / Nack
if(!HID_Read(mySensor, tmp, &read, 4000, &libUsbErr))
{
printf("Read ACK NACK failed %d\n", libUsbErr);
return 0;
}
...//Test if Ack / Nack
if(!HID_Read(mySensor, response, &read, 4000, &libUsbErr))
{
printf("Read response failed %d\n", libUsbErr);
return 0;
}
... //Prepare ACK
if(!HID_Write(mySensor, tmp, &written, 4000, &libUsbErr))
{
printf("Ack response failed %d\n", libUsbErr);
return 0;
}
...
float* temper = (float*)&response[8];
return *temper;
}
bool HID_Write(const Sensor* sens, uint8_t* repBuf, int* transferred, uint32_t timeout, int* libUsbErr)
{
if(sens == NULL || repBuf == NULL || transferred == NULL)
return returnlibUSBErr(libUsbErr, -1008); ///TODO nice error codes;
if(!sens->claimed)
return returnlibUSBErr(libUsbErr, -1012); ///TODO nice error codes;
int r = libusb_interrupt_transfer(sens->devHandle, sens->outEndpointAddr,
repBuf, sens->outRepSize, transferred, timeout);
if (r < 0)
return returnlibUSBErr(libUsbErr, r);
return returnlibUSBErr(libUsbErr, LIB_USB_OK);
}
bool HID_Read(const Sensor* sens, uint8_t* repBuf, int* read, uint32_t timeout, int* libUsbErr)
{
if(sens == NULL || read == NULL)
return returnlibUSBErr(libUsbErr, -1008); ///TODO nice error codes;
if(!sens->claimed)
return returnlibUSBErr(libUsbErr, -1012); ///TODO nice error codes;
int r = libusb_interrupt_transfer(sens->devHandle, sens->inEndpointAddr, repBuf,sens->inRepSize, read, timeout);
if (r < 0)
return returnlibUSBErr(libUsbErr, r);
return returnlibUSBErr(libUsbErr, LIB_USB_OK);
}
EDIT
If followed this instruction to monitor memory usage:
https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/using-performance-monitor-to-find-a-user-mode-memory-leak
To find the leak I used UMDH Windows tool like mentioned here:
https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/using-umdh-to-find-a-user-mode-memory-leak
The problem is that I have to use CVI NI compilter to build my application. I wasn't able to get the symbol table out of this compilter. So my heap dump diff only shows addresses.
// Each log entry has the following syntax:
//
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations
// ... stack trace ...
//
// where:
//
// BYTES_DELTA - increase in bytes between before and after log
// NEW_BYTES - bytes in after log
// OLD_BYTES - bytes in before log
// COUNT_DELTA - increase in allocations between before and after log
// NEW_COUNT - number of allocations in after log
// OLD_COUNT - number of allocations in before log
// TRACEID - decimal index of the stack trace in the trace database
// (can be used to search for allocation instances in the original
// UMDH logs).
//
+ 80000 ( 80000 - 0) 1 allocs BackTrace4920B3C
+ 1 ( 1 - 0) BackTrace4920B3C allocations
ntdll!RtlAllocateHeap+274
cvirte!LoadExternalModule+291EC
cvirte!CVIDynamicMemoryInfo+12B6
cvirte!CVIDynamicMemoryInfo+1528
cvirte!CVIDynamicMemoryInfo+1AF9
cvirte!mblen+84D
cvirte!_CVI_Resource_Acquire+116
cvirte!malloc+68
libUSB_HID!???+0 : 41DCE8
libUSB_HID!???+0 : 4E95C7
libUSB_HID!???+0 : 4C13BE
libUSB_HID!???+0 : 4BA09D
libUSB_HID!???+0 : 4C7ABA
libUSB_HID!???+0 : 4F92F0
libUSB_HID!???+0 : 4FB3BD
libUSB_HID!???+0 : 4FC50E
libUSB_HID!???+0 : 415C31
libUSB_HID!???+0 : 408847
libUSB_HID!???+0 : 402967
libUSB_HID!???+0 : 41B51E
libUSB_HID!???+0 : 41A021
kernel32!BaseThreadInitThunk+E
ntdll!__RtlUserThreadStart+70
I also replaced all free, alloc, calloc and realloc cmds within libUSB with my a own implementation tracking every single memory request. This tracking is not showing any memory leak. The amount of allocated bytes stays constant during runtime as expected. Anyway the UMDH tools shows a heap allocation difference. So I'm completely out of ideas what to test next atm.
My simple user program is not using any dynamic memory allocation.
Unfortunately, those libusb_xxx_transfer functions do stuff with malloc() internally. But it is also suppsed to do the corresponding free() just before exiting to the caller.
That memory is not normally returned to the OS, but retained in the appliction to be used in the next malloc() calls. As a result you will see some memory use in task manager.
That is why you need better tools to detect actual memory leaks, like valgrind.
Sorry guys I ported my program to minGW gcc and everythink is working as expected. It seams that my porting of libusb for CVI compiler is not completely correct. Now I use the standard dll and the memory leak is gone.

Dumping the pfn from /proc/<pid>/pagemap does not give the expected content

I'm using this code http://fivelinesofcode.blogspot.com/2014/03/how-to-translate-virtual-to-physical.html to dump the pfn related to a given virtual address taken from /proc/"pid"/maps.
Once I get the PFN, I dump it with a specific kernel module. This is a snippet of the code:
static int write_pfn(phys_addr_t pfn)
{
struct page *p;
void *v;
int s =0,ret =0;
p = pfn_to_page((pfn) >> PAGE_SHIFT);
v = kmap(p);
DBG("Writing page %d(mapped addr=0x%lx) - pfn: 0x%lx", p,v,pfn);
s = write_vaddr(v, PAGE_SIZE);
if (s != PAGE_SIZE) {
DBG("Error sending page %d(addr=0x%lx)", s,v);
return (int) s;
ret-=1;
}
kunmap(p);
return ret;
}
However, I have noticed that if I compare the PFN dumped with the kernel module with the real content of the corresponding virtual address inside the process , than the content is completely different.
Note that I dump the content of the process virtual address using the command "x" from (gdb).
Any idea? This is my kernel version:
Linux 3.14.7-rt5 #1 SMP Mon Jun 23 14:55:19 CEST 2014 x86_64 GNU/Linux
Logically your solution is correct, but i'am not sure about correctness of the pagemap reader code. Anyway, there is a way to check it. While you are inside kernel (in the context of target application) you could get page corresponding to specified VA using this kernel API:
1. mm = current->mm
2. struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
3. static inline struct page *follow_page(struct vm_area_struct *vma,
unsigned long address, unsigned int foll_flags)
4. page_to_pfn
You can implement this and compare with your results, Hope this will help.
Thank you Alex.
The problem with your solution is that some symbols are not exported (e.g. follow_page()) therefore I can't use them in my module.
However, I found this solution:
mm = ts->mm;
pgd_t * pgd = pgd_offset(mm, vaddr);
pud_t * pud = pud_offset(pgd, vaddr);
pmd_t * pmd = pmd_offset(pud, vaddr);
pte_t * pte = pte_offset_map(pmd, vaddr);
p = pte_page(*pte);
if(p)
DBG("page frame struct is # %p", p);
v = kmap(p);
DBG("Writing page 0x%lx(mapped addr=0x%lx) - pid: %d", v,vaddr,pidnr);
s = write_vaddr(v, PAGE_SIZE);
if (s != PAGE_SIZE) {
DBG("Error sending page %d(addr=0x%lx pid=%d)", v,vaddr,pidnr);
ret-=1;
}
kunmap(p);
pte_unmap(pte);

Block ram disk fails to read/write with offset

I'm creating a very very simple block RAM disk based on sbull.
So far it works fine if I read/write blocks of data using dd, but whenever I try mounting a filesystem on it (and sometimes creating a file system) my driver crashes.
After long weeks of debugging, I finally found out what is wrong, even though I can't really find a way to solve the problem. Hence my question here :)
Whenever a user space application creates a request to the device WITH AN OFFSET, the driver won't work! Let me show you the source code in order to clarify:
First of all, I'm handling requests using mk_request (not using a request_queue):
static void escsi_mk_request(struct request_queue *q, struct bio *bio)
{
struct block_device *bdev = bio->bi_bdev;
struct escsi_dev *esd = bdev->bd_disk->private_data;
int rw;
struct bio_vec *bvec;
sector_t sector;
int i;
int err = -EIO;
printk("request received nr. sectors = %lu\n",bio_sectors(bio));
sector = bio->bi_sector;
if (bio_end_sector(bio) > get_capacity(bdev->bd_disk))
goto out;
if (unlikely(bio->bi_rw & REQ_DISCARD)) {
err = 0;
goto out;
}
rw = bio_rw(bio);
if (rw == READA)
rw = READ;
bio_for_each_segment(bvec, bio, i) {
unsigned int len = bvec->bv_len;
err = esd_do_bvec(esd, bvec->bv_page, len, bvec->bv_offset, rw, sector);
if (err) {
printk("err!\n");
break;
}
sector += len >> SECTOR_SHIFT;
}
out:
bio_endio(bio, err);
}
The esd_do_bvec function:
static int esd_do_bvec(struct escsi_dev *esd, struct page *page,
unsigned int len, unsigned int off, int rw,
sector_t sector)
{
void *mem;
int err = 0;
unsigned int offset;
int i;
offset = off + sector * 512;
printk("ESD RW=%d, len=%d, off=%d, offset=%d, sector=%lu\n",rw,len,off,offset,sector);
mem = kmap_atomic(page);
if (rw == READ) {
memcpy(mem,esd->data+offset,len);
} else {
memcpy(esd->data+offset,mem,len);
}
kunmap_atomic(mem);
out:
return err;
}
OK, so basically when I read or write data using dd, the variable "off" in esd_do_bvec() is always 0, regardless of where and how many bytes I want to write. The file system obviously always performs I/O in 4KB chunks and will write a full block even when only one byte needs to be replaced.
I am sure that reads and writes are working correctly when there's no offset because I created a file that is the same size as my block RAM disk and dumped the entire file into my device using dd, then got the output of the device (also using dd), and the input and output files are exactly the same. I also wrote the same file into a brd (Linux kernel original block RAM disk driver) and the outputs are the same comparing my device and the brd device.
BUT -- in some specific situations I try to mount or create a new file system on my device and somehow it gets I/O requests with an offset, and at that point my driver fails. I assume that I'm not handling the offset properly. For example, when I try "mount -t ext2 /dev/esda":
linux-xjwl:/home/phil/escsi # mount /dev/esda -t ext2 /mnt/esda1/
mount: wrong fs type, bad option, bad superblock on /dev/esda,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so
linux-xjwl:/home/phil/escsi # dmesg|tail -n 10
[ 2239.275901] ESD RW=0, len=4096, off=0, offset=16384, sector=32
[ 2239.275947] request received nr. sectors = 8
[ 2239.275959] ESD RW=0, len=4096, off=0, offset=4096, sector=8
[ 2239.276516] request received nr. sectors = 8
[ 2239.276537] ESD RW=0, len=4096, off=0, offset=2097152, sector=4096
[ 2239.276606] request received nr. sectors = 8
[ 2239.276626] ESD RW=0, len=4096, off=0, offset=28672, sector=56
[ 2239.277535] request received nr. sectors = 2
[ 2239.277535] ESD RW=0, len=1024, off=1024, offset=2048, sector=2
[ 2239.277535] EXT4-fs (esda): VFS: Can't find ext4 filesystem
(p.s.: the output shows "EXT4" but I am running with "-t ext2")
I have checked the contents of sector n. 2 in my device and it does contain the ext2 metadata (since I ran mkfs.ext2 prior to trying to mount, of course). So I believe there's a problem with the offset. So far I can't really debug my driver because I wasn't able to come up with a request which would cause an I/O request with an offset (e.g., if I try writing a single byte into my device, Linux will read the whole block and rewrite it with only one different byte).
Hope it's not a too simple question for you.
Thanks in advance,
Phil
Please see the answer provided by Peter below.
If you're wondering what the esd_do_bvec() function looks like now, here it comes:
static int esd_do_bvec(struct escsi_dev *esd, char *buf,
unsigned int len, int rw, sector_t sector)
{
int err = 0;
unsigned int offset;
// Please notice that we STILL have an offset to deal with, but
// this offset comes in sectors and needs to be converted to a
// a byte offset.
offset = sector << SECTOR_SHIFT; // or multiply by 512
//printk("ESD RW=%d, len=%d, off=%d, offset=%d, sector=%lu\n",rw,len,off,offset,sector);
if (rw == READ) {
memcpy(buf,esd->data+offset,len);
} else {
memcpy(esd->data+offset,buf,len);
}
return err;
}
The offset per segment does not refer to an offset from the block device location, but rather an offset into the page. To cause this to be nonzero, you'll probably need to write your own C program that runs read() and write(). Allocate a page-aligned buffer, then read/write to/from different locations in that buffer, and those should show up as offsets in the bvec.
That said, LWN warns of managing this page offset manually, and recommends instead the macro bio_kmap_irq(), which is called on the bio_for_each_segment() variable bio, and takes care of the atomic kmap AND manages the offset entry as well. Source: http://lwn.net/Articles/26404/
Your code will look something like:
bio_for_each_segment(bvec, bio, i) {
unsigned int len = bvec->bv_len;
unsigned long flags;
char *buf = bio_kmap_irq(bio, &flags);
err = esd_do_bvec(esd, buf, len, rw, sector);
bio_kunmap_irq(buf, &flags);
if (err) {
printk("err!\n");
break;
}
sector += len >> SECTOR_SHIFT;
}
Of course this changes the signature of esd_do_bvec to accept the memory buffer directly rather than page/offset.

My process is mapping to a PCI memory hole, why?

I modified my kernel and walked the page table myself to get the physical address of one process's code section. I passed (current->mm)->start_code as the parameter to my function. The code for said function is listed below:
unsigned long user_va_to_pa(unsigned long v) {
pgd_t *pgd = pgd_offset(current->mm, v);
printk("process id is %d\n",current->pid);
if (!pgd_none(*pgd)) {
pud_t *pud = pud_offset(pgd, v);
if (!pud_none(*pud)) {
pmd_t *pmd = pmd_offset(pud, v);
if (!pmd_none(*pmd)) {
pte_t *pte;
pte = pte_offset_map(pmd, v);
if (pte_present(*pte)){
unsigned long pa=pte_val(*pte) & PTE_PFN_MASK;
printk(KERN_ALERT "pte value is (in vatopa) %lx\n", pte_val(*pte));
pte_unmap(pte);
return pa; //return the real physical address(page aligned)
}
else {
printk(KERN_ALERT "cannot find pte\n");
}
}
else{
printk(KERN_ALERT "cannot find pmd\n");
}
}
else {
printk(KERN_ALERT "cannot find pud\n");
}
}
else {
printk(KERN_ALERT "cannot find PGD\n");
}
return 1;
}
However, it seems that very frequently the physical address is in a PCI memory hole. I called my function as a syscall from a user application. Note that my function can printout the address like so:
pte value is (in vatopa) efe2d025
Then I opened iomem by $ cat /proc/iomem.
I could see (show part):
20000000-201fffff : reserved
20200000-40003fff : System RAM
40004000-40004fff : reserved
40005000-ced2ffff : System RAM
ced30000-dae9efff : reserved
dae9f000-daf9efff : ACPI Non-volatile Storage
daf9f000-daffefff : ACPI Tables
dafff000-df9fffff : reserved
dfa00000-febfffff : PCI Bus 0000:00
e0000000-efffffff : 0000:00:02.0
f0000000-f03fffff : 0000:00:02.0
f0400000-f0bfffff : PCI Bus 0000:02
f0c00000-f13fffff : PCI Bus 0000:04
f1400000-f1bfffff : PCI Bus 0000:04
f1c00000-f1cfffff : PCI Bus 0000:03
f1c00000-f1c01fff : 0000:03:00.0
You can see that the address efxxxxxx is totally in the PCI memory hole. However, it is supposed to be residing in system RAM, right?
This has made me unable to reserve a page attribute for that page since it's in a memory hole which has another attribute. It is very weird.
As for my system, I use a 32 bit 12.04 Ubuntu kernel on an Intel i5 Core machine with 8GB of memory.

Issue with SPI (Serial Port Comm), stuck on ioctl()

I'm trying to access a SPI sensor using the SPIDEV driver but my code gets stuck on IOCTL.
I'm running embedded Linux on the SAM9X5EK (mounting AT91SAM9G25). The device is connected to SPI0. I enabled CONFIG_SPI_SPIDEV and CONFIG_SPI_ATMEL in menuconfig and added the proper code to the BSP file:
static struct spi_board_info spidev_board_info[] {
{
.modalias = "spidev",
.max_speed_hz = 1000000,
.bus_num = 0,
.chips_select = 0,
.mode = SPI_MODE_3,
},
...
};
spi_register_board_info(spidev_board_info, ARRAY_SIZE(spidev_board_info));
1MHz is the maximum accepted by the sensor, I tried 500kHz but I get an error during Linux boot (too slow apparently). .bus_num and .chips_select should correct (I also tried all other combinations). SPI_MODE_3 I checked the datasheet for it.
I get no error while booting and devices appear correctly as /dev/spidevX.X. I manage to open the file and obtain a valid file descriptor. I'm now trying to access the device with the following code (inspired by examples I found online).
#define MY_SPIDEV_DELAY_USECS 100
// #define MY_SPIDEV_SPEED_HZ 1000000
#define MY_SPIDEV_BITS_PER_WORD 8
int spidevReadRegister(int fd,
unsigned int num_out_bytes,
unsigned char *out_buffer,
unsigned int num_in_bytes,
unsigned char *in_buffer)
{
struct spi_ioc_transfer mesg[2] = { {0}, };
uint8_t num_tr = 0;
int ret;
// Write data
mesg[0].tx_buf = (unsigned long)out_buffer;
mesg[0].rx_buf = (unsigned long)NULL;
mesg[0].len = num_out_bytes;
// mesg[0].delay_usecs = MY_SPIDEV_DELAY_USECS,
// mesg[0].speed_hz = MY_SPIDEV_SPEED_HZ;
mesg[0].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
mesg[0].cs_change = 0;
num_tr++;
// Read data
mesg[1].tx_buf = (unsigned long)NULL;
mesg[1].rx_buf = (unsigned long)in_buffer;
mesg[1].len = num_in_bytes;
// mesg[1].delay_usecs = MY_SPIDEV_DELAY_USECS,
// mesg[1].speed_hz = MY_SPIDEV_SPEED_HZ;
mesg[1].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
mesg[1].cs_change = 1;
num_tr++;
// Do the actual transmission
if(num_tr > 0)
{
ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), mesg);
if(ret == -1)
{
printf("Error: %d\n", errno);
return -1;
}
}
return 0;
}
Then I'm using this function:
#define OPTICAL_SENSOR_ADDR "/dev/spidev0.0"
...
int fd;
fd = open(OPTICAL_SENSOR_ADDR, O_RDWR);
if (fd<=0) {
printf("Device not found\n");
exit(1);
}
uint8_t buffer1[1] = {0x3a};
uint8_t buffer2[1] = {0};
spidevReadRegister(fd, 1, buffer1, 1, buffer2);
When I run it, the code get stuck on IOCTL!
I did this way because, in order to read a register on the sensor, I need to send a byte with its address in it and then get the answer back without changing CS (however, when I tried using write() and read() functions, while learning, I got the same result, stuck on them).
I'm aware that specifying .speed_hz causes a ENOPROTOOPT error on Atmel (I checked spidev.c) so I commented that part.
Why does it get stuck? I though it can be as the device is created but it actually doesn't "feel" any hardware. As I wasn't sure if hardware SPI0 corresponded to bus_num 0 or 1, I tried both, but still no success (btw, which one is it?).
UPDATE: I managed to have the SPI working! Half of it.. MOSI is transmitting the right data, but CLK doesn't start... any idea?
When I'm working with SPI I always use an oscyloscope to see the output of the io's. If you have a 4 channel scope ypu can easily debug the issue, and find out if you're axcessing the right io's, using the right speed, etc. I usually compare the signal I get to the datasheet diagram.
I think there are several issues here. First of all SPI is bidirectional. So if yo want to send something over the bus you also get something. Therefor always you have to provide a valid buffer to rx_buf and tx_buf.
Second, all members of the struct spi_ioc_transfer have to be initialized with a valid value. Otherwise they just point to some memory address and the underlying process is accessing arbitrary data, thus leading to unknown behavior.
Third, why do you use a for loop with ioctl? You already tell ioctl you haven an array of spi_ioc_transfer structs. So all defined transaction will be performed with one ioctl call.
Fourth ioctl needs a pointer to your struct array. So ioctl should look like this:
ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), &mesg);
You see there is room for improvement in your code.
This is how I do it in a c++ library for the raspberry pi. The whole library will soon be on github. I'll update my answer when it is done.
void SPIBus::spiReadWrite(std::vector<std::vector<uint8_t> > &data, uint32_t speed,
uint16_t delay, uint8_t bitsPerWord, uint8_t cs_change)
{
struct spi_ioc_transfer transfer[data.size()];
int i = 0;
for (std::vector<uint8_t> &d : data)
{
//see <linux/spi/spidev.h> for details!
transfer[i].tx_buf = reinterpret_cast<__u64>(d.data());
transfer[i].rx_buf = reinterpret_cast<__u64>(d.data());
transfer[i].len = d.size(); //number of bytes in vector
transfer[i].speed_hz = speed;
transfer[i].delay_usecs = delay;
transfer[i].bits_per_word = bitsPerWord;
transfer[i].cs_change = cs_change;
i++
}
int status = ioctl(this->fileDescriptor, SPI_IOC_MESSAGE(data.size()), &transfer);
if (status < 0)
{
std::string errMessage(strerror(errno));
throw std::runtime_error("Failed to do full duplex read/write operation "
"on SPI Bus " + this->deviceNode + ". Error message: " +
errMessage);
}
}

Resources