LCOV - code coverage report
Current view: top level - AP_HAL_Linux - SPIDevice.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 179 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 24 0.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (C) 2015  Intel Corporation. All rights reserved.
       3             :  *
       4             :  * This file is free software: you can redistribute it and/or modify it
       5             :  * under the terms of the GNU General Public License as published by the
       6             :  * Free Software Foundation, either version 3 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * This file is distributed in the hope that it will be useful, but
      10             :  * WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      12             :  * See the GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License along
      15             :  * with this program.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "SPIDevice.h"
      18             : 
      19             : #include <assert.h>
      20             : #include <errno.h>
      21             : #include <fcntl.h>
      22             : #include <linux/spi/spidev.h>
      23             : #include <stdio.h>
      24             : #include <sys/ioctl.h>
      25             : #include <sys/stat.h>
      26             : #include <sys/types.h>
      27             : #include <unistd.h>
      28             : #include <vector>
      29             : 
      30             : #include <AP_HAL/AP_HAL.h>
      31             : #include <AP_HAL/utility/OwnPtr.h>
      32             : 
      33             : #include "GPIO.h"
      34             : #include "PollerThread.h"
      35             : #include "Scheduler.h"
      36             : #include "Semaphores.h"
      37             : #include "Thread.h"
      38             : #include "Util.h"
      39             : 
      40             : #define DEBUG 0
      41             : 
      42             : extern const AP_HAL::HAL& hal;
      43             : 
      44             : namespace Linux {
      45             : 
      46             : #define MHZ (1000U*1000U)
      47             : #define KHZ (1000U)
      48             : #define SPI_CS_KERNEL -1
      49             : 
      50             : #ifdef HAL_SPI_DEVICE_LIST
      51             : SPIDesc SPIDeviceManager::_device[] = {
      52             :     HAL_SPI_DEVICE_LIST
      53             : };
      54             : #else
      55             : // empty device table
      56             : SPIDesc SPIDeviceManager::_device[] = {
      57             :     SPIDesc("**dummy**",    0, 0, SPI_MODE_3, 0, 0,  0 * MHZ, 0 * MHZ),
      58             : };
      59             : #define LINUX_SPI_DEVICE_NUM_DEVICES 1
      60             : #endif
      61             : 
      62             : #ifndef LINUX_SPI_DEVICE_NUM_DEVICES
      63             : #define LINUX_SPI_DEVICE_NUM_DEVICES ARRAY_SIZE(SPIDeviceManager::_device)
      64             : #endif
      65             : 
      66             : #define MAX_SUBDEVS 6
      67             : 
      68             : const uint8_t SPIDeviceManager::_n_device_desc = LINUX_SPI_DEVICE_NUM_DEVICES;
      69             : 
      70             : 
      71             : /* Private struct to maintain for each bus */
      72             : class SPIBus : public TimerPollable::WrapperCb {
      73             : public:
      74             :     SPIBus(uint16_t bus_);
      75             :     ~SPIBus();
      76             : 
      77             :     /*
      78             :      * TimerPollable::WrapperCb methods to take
      79             :      * and release semaphore while calling the callback
      80             :      */
      81             :     void start_cb() override;
      82             :     void end_cb() override;
      83             : 
      84             :     void open(uint16_t subdev);
      85             : 
      86             :     PollerThread thread;
      87             :     Semaphore sem;
      88             :     int fd[MAX_SUBDEVS];
      89             :     uint16_t bus;
      90             :     int16_t last_mode = -1;
      91             :     uint8_t ref;
      92             : };
      93             : 
      94           0 : SPIBus::SPIBus(uint16_t bus_)
      95           0 :     : bus(bus_)
      96             : {
      97           0 :     memset(fd, -1, sizeof(fd));
      98           0 : }
      99             : 
     100           0 : SPIBus::~SPIBus()
     101             : {
     102           0 :     for (unsigned i = 0; i < MAX_SUBDEVS; i++) {
     103           0 :         ::close(fd[i]);
     104             :     }
     105           0 : }
     106             : 
     107           0 : void SPIBus::start_cb()
     108             : {
     109           0 :     sem.take_blocking();
     110           0 : }
     111             : 
     112           0 : void SPIBus::end_cb()
     113             : {
     114           0 :     sem.give();
     115           0 : }
     116             : 
     117             : 
     118           0 : void SPIBus::open(uint16_t subdev)
     119             : {
     120             :     /* Already open by another device */
     121           0 :     if (fd[subdev] >= 0) {
     122           0 :         return;
     123             :     }
     124             : 
     125             :     char path[32];
     126           0 :     snprintf(path, sizeof(path), "/dev/spidev%u.%u", bus, subdev);
     127           0 :     fd[subdev] = ::open(path, O_RDWR | O_CLOEXEC);
     128           0 :     if (fd[subdev] < 0) {
     129           0 :         AP_HAL::panic("SPI: unable to open SPI bus %s: %s",
     130           0 :                       path, strerror(errno));
     131             :     }
     132             : }
     133             : 
     134             : 
     135           0 : SPIDevice::SPIDevice(SPIBus &bus, SPIDesc &device_desc)
     136           0 :     : _bus(bus)
     137           0 :     , _desc(device_desc)
     138             : {
     139           0 :     set_device_bus(_bus.bus);
     140           0 :     set_device_address(_desc.subdev);
     141           0 :     _speed = _desc.highspeed;
     142             : 
     143           0 :     if (_desc.cs_pin != SPI_CS_KERNEL) {
     144           0 :         _cs = hal.gpio->channel(_desc.cs_pin);
     145           0 :         if (!_cs) {
     146           0 :             AP_HAL::panic("Unable to instantiate cs pin");
     147             :         }
     148             : 
     149           0 :         _cs->mode(HAL_GPIO_OUTPUT);
     150             : 
     151             :         // do not hold the SPI bus initially
     152           0 :         _cs_release();
     153             :     }
     154           0 : }
     155             : 
     156           0 : SPIDevice::~SPIDevice()
     157             : {
     158             :     // Unregister itself from the SPIDeviceManager
     159           0 :     SPIDeviceManager::from(hal.spi)->_unregister(_bus);
     160           0 : }
     161             : 
     162           0 : bool SPIDevice::set_speed(AP_HAL::Device::Speed speed)
     163             : {
     164           0 :     switch (speed) {
     165           0 :     case AP_HAL::Device::SPEED_HIGH:
     166           0 :         _speed = _desc.highspeed;
     167           0 :         break;
     168           0 :     case AP_HAL::Device::SPEED_LOW:
     169           0 :         _speed = _desc.lowspeed;
     170           0 :         break;
     171             :     }
     172             : 
     173           0 :     return true;
     174             : }
     175             : 
     176           0 : bool SPIDevice::transfer(const uint8_t *send, uint32_t send_len,
     177             :                          uint8_t *recv, uint32_t recv_len)
     178             : {
     179           0 :     struct spi_ioc_transfer msgs[2] = { };
     180           0 :     unsigned nmsgs = 0;
     181           0 :     int fd = _bus.fd[_desc.subdev];
     182             : 
     183           0 :     if (send && send_len != 0) {
     184           0 :         msgs[nmsgs].tx_buf = (uint64_t) send;
     185           0 :         msgs[nmsgs].rx_buf = 0;
     186           0 :         msgs[nmsgs].len = send_len;
     187           0 :         msgs[nmsgs].speed_hz = _speed;
     188           0 :         msgs[nmsgs].delay_usecs = 0;
     189           0 :         msgs[nmsgs].bits_per_word = _desc.bits_per_word;
     190           0 :         msgs[nmsgs].cs_change = 0;
     191           0 :         nmsgs++;
     192             :     }
     193             : 
     194           0 :     if (recv && recv_len != 0) {
     195           0 :         msgs[nmsgs].tx_buf = 0;
     196           0 :         msgs[nmsgs].rx_buf = (uint64_t) recv;
     197           0 :         msgs[nmsgs].len = recv_len;
     198           0 :         msgs[nmsgs].speed_hz = _speed;
     199           0 :         msgs[nmsgs].delay_usecs = 0;
     200           0 :         msgs[nmsgs].bits_per_word = _desc.bits_per_word;
     201           0 :         msgs[nmsgs].cs_change = 0;
     202           0 :         nmsgs++;
     203             :     }
     204             : 
     205           0 :     if (!nmsgs) {
     206           0 :         return false;
     207             :     }
     208             : 
     209             : #if DEBUG
     210             :     if (_desc.mode == _bus.last_mode) {
     211             :         /*
     212             :           the mode in the kernel is not tied to the file descriptor,
     213             :           so there is a chance some other process has changed it since
     214             :           we last used the bus. We want to report when this happens so
     215             :           the user has a chance of figuring out when there is
     216             :           conflicted use of the SPI bus. Unfortunately this costs us
     217             :           an extra syscall per transfer.
     218             :          */
     219             :         uint8_t current_mode;
     220             :         if (ioctl(fd, SPI_IOC_RD_MODE, &current_mode) < 0) {
     221             :             hal.console->printf("SPIDevice: error on getting mode fd=%d (%s)\n",
     222             :                                 fd, strerror(errno));
     223             :             _bus.last_mode = -1;
     224             :         } else if (current_mode != _bus.last_mode) {
     225             :             hal.console->printf("SPIDevice: bus mode conflict fd=%d mode=%u/%u\n",
     226             :                                 fd, (unsigned)_bus.last_mode, (unsigned)current_mode);
     227             :             _bus.last_mode = -1;
     228             :         }
     229             :     }
     230             : #endif
     231             : 
     232             :     int r;
     233           0 :     if (_desc.mode != _bus.last_mode) {
     234           0 :         r = ioctl(fd, SPI_IOC_WR_MODE, &_desc.mode);
     235           0 :         if (r < 0) {
     236           0 :             hal.console->printf("SPIDevice: error on setting mode fd=%d (%s)\n",
     237           0 :                                 fd, strerror(errno));
     238           0 :             return false;
     239             :         }
     240           0 :         _bus.last_mode = _desc.mode;
     241             :     }
     242             : 
     243           0 :     _cs_assert();
     244           0 :     r = ioctl(fd, SPI_IOC_MESSAGE(nmsgs), &msgs);
     245           0 :     _cs_release();
     246             : 
     247           0 :     if (r == -1) {
     248           0 :         hal.console->printf("SPIDevice: error transferring data fd=%d (%s)\n",
     249           0 :                             fd, strerror(errno));
     250           0 :         return false;
     251             :     }
     252             : 
     253           0 :     return true;
     254             : }
     255             : 
     256           0 : bool SPIDevice::transfer_fullduplex(const uint8_t *send, uint8_t *recv,
     257             :                                     uint32_t len)
     258             : {
     259           0 :     struct spi_ioc_transfer msgs[1] = { };
     260           0 :     int fd = _bus.fd[_desc.subdev];
     261             : 
     262           0 :     if (!send || !recv || len == 0) {
     263           0 :         return false;
     264             :     }
     265             : 
     266           0 :     msgs[0].tx_buf = (uint64_t) send;
     267           0 :     msgs[0].rx_buf = (uint64_t) recv;
     268           0 :     msgs[0].len = len;
     269           0 :     msgs[0].speed_hz = _speed;
     270           0 :     msgs[0].delay_usecs = 0;
     271           0 :     msgs[0].bits_per_word = _desc.bits_per_word;
     272           0 :     msgs[0].cs_change = 0;
     273             : 
     274           0 :     int r = ioctl(fd, SPI_IOC_WR_MODE, &_desc.mode);
     275           0 :     if (r < 0) {
     276           0 :         hal.console->printf("SPIDevice: error on setting mode fd=%d (%s)\n",
     277           0 :                             fd, strerror(errno));
     278           0 :         return false;
     279             :     }
     280             : 
     281           0 :     _cs_assert();
     282           0 :     r = ioctl(fd, SPI_IOC_MESSAGE(1), &msgs);
     283           0 :     _cs_release();
     284             : 
     285           0 :     if (r == -1) {
     286           0 :         hal.console->printf("SPIDevice: error transferring data fd=%d (%s)\n",
     287           0 :                             fd, strerror(errno));
     288           0 :         return false;
     289             :     }
     290             : 
     291           0 :     return true;
     292             : }
     293             : 
     294           0 : bool SPIDevice::transfer_fullduplex(uint8_t *send_recv, uint32_t len)
     295             : {
     296           0 :     return transfer_fullduplex(send_recv, send_recv, len);
     297             : }
     298             : 
     299           0 : void SPIDevice::_cs_assert()
     300             : {
     301           0 :     if (_desc.cs_pin == SPI_CS_KERNEL) {
     302           0 :         return;
     303             :     }
     304             : 
     305           0 :     _cs->write(0);
     306             : }
     307             : 
     308           0 : void SPIDevice::_cs_release()
     309             : {
     310           0 :     if (_desc.cs_pin == SPI_CS_KERNEL) {
     311           0 :         return;
     312             :     }
     313             : 
     314           0 :     _cs->write(1);
     315             : }
     316             : 
     317           0 : AP_HAL::Semaphore *SPIDevice::get_semaphore()
     318             : {
     319           0 :     return &_bus.sem;
     320             : }
     321             : 
     322           0 : AP_HAL::Device::PeriodicHandle SPIDevice::register_periodic_callback(
     323             :     uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)
     324             : {
     325           0 :     TimerPollable *p = _bus.thread.add_timer(cb, &_bus, period_usec);
     326           0 :     if (!p) {
     327           0 :         AP_HAL::panic("Could not create periodic callback");
     328             :     }
     329             : 
     330           0 :     if (!_bus.thread.is_started()) {
     331             :         char name[16];
     332           0 :         snprintf(name, sizeof(name), "ap-spi-%u", _bus.bus);
     333             : 
     334           0 :         _bus.thread.set_stack_size(AP_LINUX_SENSORS_STACK_SIZE);
     335           0 :         _bus.thread.start(name, AP_LINUX_SENSORS_SCHED_POLICY,
     336             :                           AP_LINUX_SENSORS_SCHED_PRIO);
     337             :     }
     338             : 
     339           0 :     return static_cast<AP_HAL::Device::PeriodicHandle>(p);
     340             : }
     341             : 
     342           0 : bool SPIDevice::adjust_periodic_callback(
     343             :     AP_HAL::Device::PeriodicHandle h, uint32_t period_usec)
     344             : {
     345           0 :     return _bus.thread.adjust_timer(static_cast<TimerPollable*>(h), period_usec);
     346             : }
     347             : 
     348             : 
     349             : AP_HAL::SPIDevice *
     350           0 : SPIDeviceManager::get_device_ptr(const char *name)
     351             : {
     352           0 :     SPIDesc *desc = nullptr;
     353             : 
     354             :     /* Find the bus description in the table */
     355           0 :     for (uint8_t i = 0; i < _n_device_desc; i++) {
     356           0 :         if (!strcmp(_device[i].name, name)) {
     357           0 :             desc = &_device[i];
     358           0 :             break;
     359             :         }
     360             :     }
     361             : 
     362           0 :     if (!desc) {
     363           0 :         return nullptr;
     364             :     }
     365             : 
     366             :     /* Find if bus already exists */
     367           0 :     for (uint8_t i = 0, n = _buses.size(); i < n; i++) {
     368           0 :         if (_buses[i]->bus == desc->bus) {
     369           0 :             return _create_device(*_buses[i], *desc);
     370             :         }
     371             :     }
     372             : 
     373             :     /* Bus not found for this device, create a new one */
     374           0 :     AP_HAL::OwnPtr<SPIBus> b{new SPIBus(desc->bus)};
     375           0 :     if (!b) {
     376           0 :         return nullptr;
     377             :     }
     378             : 
     379           0 :     auto dev = _create_device(*b, *desc);
     380           0 :     if (!dev) {
     381           0 :         return nullptr;
     382             :     }
     383             : 
     384           0 :     _buses.push_back(b.leak());
     385             : 
     386           0 :     return dev;
     387           0 : }
     388             : 
     389           0 : uint8_t SPIDeviceManager::get_count()
     390             : {
     391           0 :    return _n_device_desc;
     392             : }
     393             : 
     394           0 : const char* SPIDeviceManager::get_device_name(uint8_t idx)
     395             : {
     396           0 :     return _device[idx].name;
     397             : }
     398             : 
     399             : /* Create a new device increasing the bus reference */
     400             : AP_HAL::SPIDevice *
     401           0 : SPIDeviceManager::_create_device(SPIBus &b, SPIDesc &desc) const
     402             : {
     403             :     // Ensure bus is open
     404           0 :     b.open(desc.subdev);
     405             : 
     406           0 :     auto *dev = NEW_NOTHROW SPIDevice(b, desc);
     407           0 :     if (!dev) {
     408           0 :         return nullptr;
     409             :     }
     410             : 
     411           0 :     b.ref++;
     412             : 
     413           0 :     return dev;
     414             : }
     415             : 
     416           0 : void SPIDeviceManager::_unregister(SPIBus &b)
     417             : {
     418           0 :     if (b.ref == 0 || --b.ref > 0) {
     419           0 :         return;
     420             :     }
     421             : 
     422           0 :     for (auto it = _buses.begin(); it != _buses.end(); it++) {
     423           0 :         if ((*it)->bus == b.bus) {
     424           0 :             _buses.erase(it);
     425           0 :             delete &b;
     426           0 :             break;
     427             :         }
     428             :     }
     429             : }
     430             : 
     431           0 : void SPIDeviceManager::teardown()
     432             : {
     433           0 :     for (auto it = _buses.begin(); it != _buses.end(); it++) {
     434             :         /* Try to stop thread - it may not even be started yet */
     435           0 :         (*it)->thread.stop();
     436             :     }
     437             : 
     438           0 :     for (auto it = _buses.begin(); it != _buses.end(); it++) {
     439             :         /* Try to join thread - failing is normal if thread was not started */
     440           0 :         (*it)->thread.join();
     441             :     }
     442           0 : }
     443             : 
     444             : }

Generated by: LCOV version 1.14