aboutsummaryrefslogtreecommitdiff
path: root/hsm-gpio
diff options
context:
space:
mode:
Diffstat (limited to 'hsm-gpio')
-rw-r--r--hsm-gpio/Hsm/GPIO.hs233
-rw-r--r--hsm-gpio/Hsm/GPIO/Lib.hsc116
-rw-r--r--hsm-gpio/hsm-gpio.cabal12
3 files changed, 260 insertions, 101 deletions
diff --git a/hsm-gpio/Hsm/GPIO.hs b/hsm-gpio/Hsm/GPIO.hs
index bc08ef5..dd69122 100644
--- a/hsm-gpio/Hsm/GPIO.hs
+++ b/hsm-gpio/Hsm/GPIO.hs
@@ -1,120 +1,163 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TypeFamilies #-}
module Hsm.GPIO
- ( GPIO(..)
- , GPIOEffect
- , toggle
- , runGPIO
+ ( G.active
+ , G.inactive
+ , G.LineRequest
+ , GPIO(..)
+ , setPins
+ , setAllPins
+ , allocateGPIO
) where
-import Data.Aeson (FromJSON)
-import Data.Kind (Type)
-import Data.List (intercalate)
-import Data.Set (Set, toList, unions)
-import Data.String (IsString)
+import Control.IO.Region (Region, alloc, alloc_, defer, free)
+import Control.Monad (forM_, void)
+import Data.ByteString (useAsCString)
import Data.Text (Text, pack)
-import Effectful (Dispatch(Static), DispatchOf, Eff, IOE, (:>))
-import Effectful.Dispatch.Static qualified as E
-import Effectful.Exception (finally)
-import Effectful.Log (Log, localDomain, logTrace_)
-import GHC.Generics (Generic)
-import Hsm.Core.Log (flushLogger)
-import System.Process (callCommand)
+import Data.Text.Encoding (encodeUtf8)
+import Data.Vector.Storable qualified as V
+import Foreign.C.Types (CSize(CSize), CUInt(CUInt))
+import Foreign.Ptr (Ptr)
+import Hsm.GPIO.Lib qualified as G
+import Hsm.Log qualified as L
+
+logMsg :: Text -> IO ()
+logMsg = L.logMsg ["gpio"]
--- Monofunctional GPIO pins
data GPIO
- = GPIO5
+ = GPIO2
+ | GPIO3
+ | GPIO4
+ | GPIO5
| GPIO6
+ | GPIO7
+ | GPIO8
+ | GPIO9
+ | GPIO10
+ | GPIO11
+ | GPIO12
+ | GPIO13
+ | GPIO14
+ | GPIO15
| GPIO16
| GPIO17
+ -- | GPIO18 -- reserved for PWM
+ -- | GPIO19 -- reserved for PWM
+ | GPIO20
+ | GPIO21
| GPIO22
| GPIO23
| GPIO24
| GPIO25
| GPIO26
| GPIO27
- deriving (Eq, FromJSON, Generic, Ord, Read, Show)
-
-data GPIOEffect key a b
-
-type instance DispatchOf (GPIOEffect key) = Static E.WithSideEffects
+ deriving (Bounded, Enum, Show)
--- Effect state is a mapping function from type `key` to a `Set` of GPIO pins.
--- This enables `key`s of any type to control many pins simultaneously. Using
--- a function (instead of `Data.Map`) ensures all keys map to pins, given the
--- provided function is total.
-newtype instance E.StaticRep (GPIOEffect (key :: Type)) =
- GPIOEffect (key -> Set GPIO)
+pinLine :: GPIO -> CUInt
+pinLine = CUInt . read . drop 4 . show
-domain :: Text
-domain = "gpio"
+allPins :: [GPIO]
+allPins = [minBound .. maxBound]
-stateStr :: IsString a => Bool -> a
-stateStr True = "on"
-stateStr False = "off"
-
--- To control the pins, I use a subprocess call to `gpioset`. In the future
--- I'd prefer wrapping `libgpiod` directly. It looks like no one has created a
--- C wrapper yet, I might do it if I get bored. :)
-gpioset :: Log :> es => Bool -> Set GPIO -> [Int] -> Eff es ()
-gpioset state gpios periods = do
- localDomain domain $ logTrace_ $ "Calling command: " <> pack command
- E.unsafeEff_ $ callCommand command
- where
- lineArg gpio = show gpio <> "=" <> stateStr state <> " "
- command =
- "gpioset -t"
- <> intercalate "," (show <$> periods)
- <> " "
- <> concatMap lineArg (toList gpios)
+allLines :: [CUInt]
+allLines = pinLine <$> allPins
-logReport ::
- (Log :> es, Show key) => Bool -> key -> [Int] -> Set GPIO -> Eff es ()
-logReport state key periods gpios = do
- localDomain domain $ logTrace_ report
- flushLogger
- where
- report =
- "Setting pins "
- <> pack (show gpios)
- <> " mapped to key "
- <> pack (show key)
+setPins :: Ptr G.LineRequest -> [GPIO] -> G.LineValue -> IO ()
+setPins lineRequest pins lineValue = do
+ logMsg
+ $ "Setting pin(s) "
+ <> pack (show pins)
<> " to state "
- <> pack (show state)
- <> " using periods "
- <> pack (show periods)
+ <> pack (show lineValue)
+ forM_ pins $ \pin -> G.lineRequestSetValue lineRequest (pinLine pin) lineValue
-toggle ::
- (GPIOEffect key :> es, Log :> es, Show key)
- => Bool
- -> key
- -> [Int]
- -> Eff es ()
-toggle state key periods = do
- GPIOEffect mapper <- E.getStaticRep
- set $ mapper key
- where
- set gpios = do
- logReport state key periods gpios
- gpioset state gpios periods
+setAllPins :: Ptr G.LineRequest -> G.LineValue -> IO ()
+setAllPins lineRequest lineValue = do
+ logMsg
+ $ "Setting all pins "
+ <> pack (show allPins)
+ <> " to state "
+ <> pack (show lineValue)
+ void
+ $ V.unsafeWith (V.replicate (length allPins) lineValue)
+ $ G.lineRequestSetValues lineRequest
-runGPIO ::
- (IOE :> es, Log :> es, Bounded key, Enum key)
- => (key -> Set GPIO)
- -> Eff (GPIOEffect key : es) a
- -> Eff es a
-runGPIO mapper action =
- E.evalStaticRep (GPIOEffect mapper) $ finally action release
+allocateGPIO :: Region -> Text -> IO (Ptr G.LineRequest)
+allocateGPIO region consumer = do
+ (chip, chipKey) <- allocateChip
+ (lineSettings, lineSettingsKey) <- allocateLineSettings
+ (lineConfig, lineConfigKey) <- allocateLineConfig lineSettings
+ (requestConfig, requestConfigKey) <- allocateRequestConfig
+ lineRequest <- allocateLineRequest chip requestConfig lineConfig
+ free requestConfigKey
+ free lineConfigKey
+ free lineSettingsKey
+ free chipKey
+ defer region $ setAllPins lineRequest G.inactive
+ return lineRequest
where
- gpios = unions $ mapper <$> [minBound .. maxBound]
- endReport =
- "Setting all mapped pins "
- <> pack (show gpios)
- <> " to state "
- <> stateStr False
- release = do
- localDomain domain $ logTrace_ endReport
- gpioset False gpios [0]
+ chipPath = "/dev/gpiochip0"
+ -- GPIO chip
+ chipOpen = do
+ logMsg $ "Opening GPIO chip " <> chipPath
+ useAsCString (encodeUtf8 chipPath) G.chipOpen
+ chipClose chip = do
+ logMsg $ "Closing GPIO chip " <> chipPath
+ G.chipClose chip
+ allocateChip = alloc region chipOpen chipClose
+ -- Line settings
+ lineSettingsNew = do
+ logMsg "Allocating line settings"
+ lineSettings <- G.lineSettingsNew
+ logMsg $ "With direction set to " <> pack (show G.output)
+ void $ G.lineSettingsSetDirection lineSettings G.output
+ logMsg $ "With output set to " <> pack (show G.inactive)
+ void $ G.lineSettingsSetOutputValue lineSettings G.inactive
+ return lineSettings
+ lineSettingsFree lineSettings = do
+ logMsg "Freeing line settings"
+ G.lineSettingsFree lineSettings
+ allocateLineSettings = alloc region lineSettingsNew lineSettingsFree
+ -- Line config
+ lineConfigNew lineSettings = do
+ logMsg "Allocating line config"
+ logMsg $ "With GPIO pins " <> pack (show allPins)
+ lineConfig <- G.lineConfigNew
+ void
+ $ V.unsafeWith (V.fromList allLines)
+ $ \pinsVector ->
+ G.lineConfigAddLineSettings
+ lineConfig
+ pinsVector
+ (CSize $ fromIntegral $ length allPins)
+ lineSettings
+ return lineConfig
+ lineConfigFree lineConfig = do
+ logMsg "Freeing line config"
+ G.lineConfigFree lineConfig
+ allocateLineConfig lineSettings =
+ alloc region (lineConfigNew lineSettings) lineConfigFree
+ -- Request config
+ requestConfigNew = do
+ logMsg "Allocating request config"
+ logMsg $ "With consumer " <> consumer
+ requestConfig <- G.requestConfigNew
+ useAsCString (encodeUtf8 consumer)
+ $ G.requestConfigSetConsumer requestConfig
+ return requestConfig
+ requestConfigFree requestConfig = do
+ logMsg "Freeing request config"
+ G.requestConfigFree requestConfig
+ allocateRequestConfig = alloc region requestConfigNew requestConfigFree
+ -- Line request
+ requestLines chip requestConfig lineConfig = do
+ logMsg "Allocating line request"
+ G.requestLines chip requestConfig lineConfig
+ lineRequestRelease lineRequest = do
+ logMsg "Releasing line request"
+ G.lineRequestRelease lineRequest
+ allocateLineRequest chip requestConfig lineConfig =
+ alloc_
+ region
+ (requestLines chip requestConfig lineConfig)
+ lineRequestRelease
diff --git a/hsm-gpio/Hsm/GPIO/Lib.hsc b/hsm-gpio/Hsm/GPIO/Lib.hsc
new file mode 100644
index 0000000..6716f3a
--- /dev/null
+++ b/hsm-gpio/Hsm/GPIO/Lib.hsc
@@ -0,0 +1,116 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+-- This module provides C bindings to the `gpiod` library for direct GPIO pin
+-- control. It includes bindings only for the C functions that are currently
+-- used. In the future, creating a complete set of bindings for the entire
+-- `gpiod` library as an external package would be a valuable contribution to
+-- Hackage.
+
+module Hsm.GPIO.Lib
+ ( chipOpen
+ , chipClose
+ , input
+ , output
+ , LineValue
+ , active
+ , inactive
+ , lineSettingsNew
+ , lineSettingsFree
+ , lineSettingsSetDirection
+ , lineSettingsSetOutputValue
+ , lineConfigNew
+ , lineConfigFree
+ , lineConfigAddLineSettings
+ , requestConfigNew
+ , requestConfigFree
+ , requestConfigSetConsumer
+ , requestLines
+ , LineRequest
+ , lineRequestRelease
+ , lineRequestSetValue
+ , lineRequestSetValues
+ ) where
+
+#include <gpiod.h>
+
+import Foreign.C.String (CString)
+import Foreign.C.Types (CInt(CInt), CSize(CSize), CUInt(CUInt))
+import Foreign.Ptr (Ptr)
+import Foreign.Storable (Storable)
+
+data Chip
+
+foreign import ccall unsafe "gpiod.h gpiod_chip_open"
+ chipOpen :: CString -> IO (Ptr Chip)
+
+foreign import ccall unsafe "gpiod.h gpiod_chip_close"
+ chipClose :: Ptr Chip -> IO ()
+
+data LineSettings
+
+newtype LineDirection =
+ LineDirection CInt
+ deriving (Show)
+
+#{enum LineDirection, LineDirection
+ , input = GPIOD_LINE_DIRECTION_INPUT
+ , output = GPIOD_LINE_DIRECTION_OUTPUT
+}
+
+newtype LineValue =
+ LineValue CInt
+ deriving (Show, Storable)
+
+#{enum LineValue, LineValue
+ , active = GPIOD_LINE_VALUE_ACTIVE
+ , inactive = GPIOD_LINE_VALUE_INACTIVE
+}
+
+foreign import ccall unsafe "gpiod.h gpiod_line_settings_new"
+ lineSettingsNew :: IO (Ptr LineSettings)
+
+foreign import ccall unsafe "gpiod.h gpiod_line_settings_free"
+ lineSettingsFree :: Ptr LineSettings -> IO ()
+
+foreign import ccall unsafe "gpiod.h gpiod_line_settings_set_direction"
+ lineSettingsSetDirection :: Ptr LineSettings -> LineDirection -> IO CInt
+
+foreign import ccall unsafe "gpiod.h gpiod_line_settings_set_output_value"
+ lineSettingsSetOutputValue :: Ptr LineSettings -> LineValue -> IO CInt
+
+data LineConfig
+
+foreign import ccall unsafe "gpiod.h gpiod_line_config_new"
+ lineConfigNew :: IO (Ptr LineConfig)
+
+foreign import ccall unsafe "gpiod.h gpiod_line_config_free"
+ lineConfigFree :: Ptr LineConfig -> IO ()
+
+foreign import ccall unsafe "gpiod.d gpiod_line_config_add_line_settings"
+ lineConfigAddLineSettings :: Ptr LineConfig -> Ptr CUInt -> CSize -> Ptr LineSettings -> IO CInt
+
+data RequestConfig
+
+foreign import ccall unsafe "gpiod.h gpiod_request_config_new"
+ requestConfigNew :: IO (Ptr RequestConfig)
+
+foreign import ccall unsafe "gpiod.h gpiod_request_config_free"
+ requestConfigFree :: Ptr RequestConfig -> IO ()
+
+foreign import ccall unsafe "gpiod.h gpiod_request_config_set_consumer"
+ requestConfigSetConsumer :: Ptr RequestConfig -> CString -> IO ()
+
+data LineRequest
+
+foreign import ccall unsafe "gpiod.h gpiod_chip_request_lines"
+ requestLines :: Ptr Chip -> Ptr RequestConfig -> Ptr LineConfig -> IO (Ptr LineRequest)
+
+foreign import ccall unsafe "gpiod.h gpiod_line_request_release"
+ lineRequestRelease :: Ptr LineRequest -> IO ()
+
+foreign import ccall unsafe "gpiod.h gpiod_line_request_set_value"
+ lineRequestSetValue :: Ptr LineRequest -> CUInt -> LineValue -> IO CInt
+
+foreign import ccall unsafe "gpiod.h gpiod_line_request_set_values"
+ lineRequestSetValues :: Ptr LineRequest -> Ptr LineValue -> IO CInt
diff --git a/hsm-gpio/hsm-gpio.cabal b/hsm-gpio/hsm-gpio.cabal
index 8ff3e13..786977a 100644
--- a/hsm-gpio/hsm-gpio.cabal
+++ b/hsm-gpio/hsm-gpio.cabal
@@ -7,15 +7,15 @@ version: 0.1.0.0
library
build-depends:
- , aeson
, base
- , containers
- , effectful-core
- , hsm-core
- , log-effectful
- , process
+ , bytestring
+ , hsm-log
+ , io-region
, text
+ , vector
exposed-modules: Hsm.GPIO
+ other-modules: Hsm.GPIO.Lib
ghc-options: -Wall -Wunused-packages
+ extra-libraries: gpiod
default-language: GHC2021