packages feed

data-svd-0.1.2.0: src/Data/SVD/Util.hs

{-# LANGUAGE RecordWildCards #-}

module Data.SVD.Util
  ( addReservedFields
  , procFields
  , continuityCheck
  , checkDeviceRegisterContinuity
  , mapPeriphs
  , mapRegs
  , mapFields
  , mapDevFields
  , getPeriphByGroup
  , getPeriph
  , getPeriphMay
  , getPeriphRegMay
  , getPeriphFollow
  , getPeriphRegs
  , getPeriphReg
  , getPeriphRegAddr
  , getPeriphRegFields
  , getRegFields
  , getFieldVal
  , getFieldValues
  , anyReservedSet
  , getDevMemMap
  , registerNames
  , fieldNames
  -- * Sorting
  , sortDeviceByAddresses
  , sortDeviceByNames
  -- * Interrupts
  , fillMissingInterrupts
  ) where

import Control.Lens ((^.), over, set, view)
import Control.Monad (liftM2)
import Data.Bits (Bits, shiftR, (.&.))
import Data.SVD.Lens
import Data.SVD.Types

import qualified Data.Char
import qualified Data.Bits.Pretty
import qualified Data.Either
import qualified Data.List
import qualified Data.Maybe
import qualified Data.Set
import qualified Safe

-- | Find holes in registers and create corresponding reserved fields for these
--
-- First finds missing bits and then merges them to single reserved field
procFields :: Register -> [Field]
procFields Register{..} =
    dataIfSingleReserved
  $ reverse
  $ sortByOffset (regFields ++ missingAsReserved)
  where
    missingAsReserved =
      mkReserved
      $ conts
      $ Data.Set.toList missing

    mkReserved =
      map
        $ \(offset', width') ->
          Field
            { fieldName = "_"
            , fieldDescription = "(Reserved)"
            , fieldDimension = Nothing
            , fieldBitOffset = offset'
            , fieldBitWidth = width'
            , fieldReserved = True
            , fieldRegType = Nothing
            }

    conts x = case cont x of
      [] -> []
      s -> (head s, length s) : conts (drop (length s) x)

    -- find longest increasing sequence
    cont :: (Eq a, Num a) => [a] -> [a]
    cont (x:y:xs) | x + 1 == y = x : cont (y:xs)
    cont (x:_)  = [x]
    cont [] = []

    missing = allRegs `Data.Set.difference` existing

    allRegs = Data.Set.fromList [0..(regSize - 1)]

    existing =
      Data.Set.fromList
      $ flip concatMap (sortByOffset regFields)
      $ \Field{..} -> [fieldBitOffset .. (fieldBitOffset + fieldBitWidth - 1)]

    sortByOffset = Data.List.sortOn fieldBitOffset

    -- this handles a case when there are no fields and code above
    -- creates a single full-sized reserved field
    -- which we turn into non-reserved "data" field
    dataIfSingleReserved [f] | fieldReserved f =
      [ f {
            fieldName = "DATA"
          , fieldReserved = False
          }
      ]
    dataIfSingleReserved fs = fs

-- | Fill in reserved fields for whole @Device@
addReservedFields :: Device -> Device
addReservedFields =
  over
    (peripherals . traverse . registers . traverse)
    procRegister
  where
    procRegister r = set fields (procFields r) r

-- | Walk processed register fields top to bottom
-- checking that the register is exactly n bits long
continuityCheck :: Register -> Bool
continuityCheck Register{..} = go regFields regSize
  where
  go [] 0 = True
  go (x:xs) remainingBits
    | fieldBitOffset x + fieldBitWidth x == remainingBits
    = go xs (remainingBits - fieldBitWidth x)
  go _ _ = False

-- | Walk processed register fields top to bottom
-- checking that the register is exactly n bits long
continuityCheckReg
  :: Device
  -> Peripheral
  -> Register
  -> Either String Register

-- Some ignores
-- TIM2.CNT, TIM5.CNT is 32 bit but has an aliased UIFCPY field
continuityCheckReg d p r
  | d ^. name `elem` [ "STM32F730", "STM32F745", "STM32F750", "STM32F765"
                     , "STM32F7x2", "STM32F7x3", "STM32F7x6", "STM32F7x7", "STM32F7x9" ]
  && p ^. name `elem` [ "TIM2", "TIM5" ]
  && r ^. name == "CNT" = pure r
-- similar for Gs
--
continuityCheckReg d p r
  | d ^. name `elem` [ "STM32G431xx", "STM32G441xx", "STM32G471xx", "STM32G473xx"
                     , "STM32G474xx", "STM32G483xx", "STM32G484xx", "STM32G491xx", "STM32G4A1xx" ]
  && p ^. name == "TIM2" && r ^. name == "CNT" = pure r
-- G4 TIM2.CCR5, might be a bug in stm32-rs
continuityCheckReg d p r
  | d ^. name `elem` [ "STM32G431xx", "STM32G441xx", "STM32G471xx", "STM32G473xx"
                     , "STM32G474xx", "STM32G483xx", "STM32G484xx", "STM32G491xx", "STM32G4A1xx" ]
  && p ^. name == "TIM2" && r ^. name == "CCR5" = pure r
-- F101, F103 TIM10.CCMR1_Output aliased OC1FE field
continuityCheckReg d p r
  | d ^. name `elem` [ "STM32F101", "STM32F103" ]
  && p ^. name == "TIM10" && r ^. name == "CCMR1_Output" = pure r
continuityCheckReg d p r
  | d ^. name == "STM32H73x"
  && p ^. name == "CRYP" && r ^. name == "K2LR" = pure r
continuityCheckReg d p r
  | (d ^. name == "STM32L0x2" || d ^. name == "STM32L0x3")
  && p ^.name == "PWR" && r ^. name == "CR" = pure r
continuityCheckReg d p r
  | d ^. name == "STM32L4P5"
  && p ^.name == "TIM15" && r ^. name == "SR" = pure r
continuityCheckReg d p r
  | d ^. name == "STM32L4P5"
  && p ^.name == "SAI1" && r ^. name == "CR1" = pure r
continuityCheckReg d p r
  | d ^. name == "STM32L4P5"
  && p ^.name == "FLASH" && r ^. name == "ECCR" = pure r
continuityCheckReg d p r
  | d ^. name == "STM32WB55"
  && p ^.name == "TIM2" && r ^. name == "CNT" = pure r
-- lots of errors in TIMx, GPDMA1
continuityCheckReg d _p r
  | "STM32H5" `Data.List.isPrefixOf` (d ^. name) = pure r
continuityCheckReg d p r =
  go
    ( reverse
    $ Data.List.sortOn
        (view bitOffset)
        (r ^. fields)
    )
    (r ^. size)
  where
  go [] 0 = pure r

  go (x:xs) remainingBits
    | x ^. bitOffset + x ^. bitWidth == remainingBits
    = go xs (remainingBits - (x ^. bitWidth))

  go _xs remainingBits =
    Left
    $ "Continuity check failed with remaining bits: "
    <> show remainingBits
    <> " for device "
    <> d ^. name
    <> " for "
    <> p ^. name
    <> "."
    <> r ^. name

-- | Check all devices registers for continuity
checkDeviceRegisterContinuity
  :: Device
  -> Either String Device
checkDeviceRegisterContinuity d =
  let
    res =
      concatMap
        (\p ->
          map
            (continuityCheckReg d p)
            (p ^. registers)
        )
      (devicePeripherals d)
  in
    case res of
      _ | all Data.Either.isRight res -> pure d
      _ | otherwise -> Left $ unlines $ Data.Either.lefts res

mapPeriphs :: (Peripheral -> b) -> Device -> [b]
mapPeriphs f Device{..} = map f devicePeripherals

mapRegs :: (Register -> b) -> Peripheral -> [b]
mapRegs f Peripheral{..} = map f periphRegisters

mapFields :: (Field -> b) -> Register -> [b]
mapFields f Register{..} = map f regFields

mapDevFields :: (Field -> b) -> Device -> [b]
mapDevFields f d =
    concat
  $ concat
  $ flip mapPeriphs d
  $ mapRegs
  $ mapFields f

-- | Get peripheral by groupName
getPeriphByGroup :: String -> Device -> Peripheral
getPeriphByGroup name' dev =
  case filterLowerBy name' periphGroupName (devicePeripherals dev) of
    [] -> error $ "getPeriphByGroup, peripheral " ++ name' ++ " not found"
    [p] -> p
    ps -> case filter (Data.Maybe.isNothing . periphDerivedFrom) ps of
      [] -> error $ "getPeriphByGroup: No non-derived peripheral found for " ++ name'
      [p] -> p
      (p:_xs) -> p
       -- TODO: warn?
       -- error $ "getPeriphByGroup: Multiple non-derived peripheral found for " ++ name

-- | Get peripheral by name
getPeriph :: String -> Device -> Peripheral
getPeriph name' dev =
  Safe.headNote ("getPeriph " ++ name')
  . filterLowerBy name' periphName $ devicePeripherals dev

-- | Get peripheral by name iff found, Nothing otherwise
getPeriphMay :: String -> Device -> Maybe Peripheral
getPeriphMay name' dev =
  Safe.headMay
  . filterLowerBy name' periphName $ devicePeripherals dev

-- | Get register of the peripheral by their names iff found, Nothing otherwise
getPeriphRegMay :: String -> Peripheral -> Maybe Register
getPeriphRegMay rName =
  Safe.headMay
  . filterLowerBy rName regName . periphRegisters

-- | Filter elements matching lowercased `eqTo` after applying `by`
filterLowerBy :: String -> (a -> String) -> [a] -> [a]
filterLowerBy eqTo by =
  filter
  $ (== map Data.Char.toLower eqTo)
    . map Data.Char.toLower
    . by

-- | Get peripheral by name or its parent peripheral if it's
-- a derived peripheral (for example USART2 is typically derived from USART1)
getPeriphFollow :: String -> Device -> Either String Peripheral
getPeriphFollow pName dev = case getPeriphMay pName dev of
  Nothing -> Left $ "No peripheral found: " ++ pName
  Just p  -> case periphDerivedFrom p of
    Nothing -> Right p
    Just fromName -> case getPeriphMay fromName dev of
      Nothing -> Left $ "Parent peripheral not found: " ++ fromName ++ " for peripheral " ++ pName
      Just parentPeriph -> Right parentPeriph

-- | Get registers of the peripheral
getPeriphRegs :: String -> Device -> Either String [Register]
getPeriphRegs pName dev = periphRegisters <$> getPeriphFollow pName dev

-- | Get specific register of the peripheral
-- Follows derived from.
getPeriphReg :: String -> String -> Device -> Either String Register
getPeriphReg pName rName dev =
  either
    Left
    (maybeToEither errMsg . getPeriphRegMay rName)
    $ getPeriphFollow pName dev
  where
    errMsg = "No register found: " ++ rName ++ " for peripheral " ++ pName

maybeToEither :: a -> Maybe b -> Either a b
maybeToEither msg m = case m of
  Just x -> Right x
  Nothing -> Left msg

-- | Get address of the specific register of the peripheral with `pName`
getPeriphRegAddr :: String -> String -> Device -> Either String Int
getPeriphRegAddr pName rName dev =
  (\p r -> periphBaseAddress p + regAddressOffset r)
  <$> maybeToEither errMsg (getPeriphMay pName dev)
  <*> getPeriphReg pName rName dev
  where
    errMsg = "No peripheral found " ++ pName

-- | Get fields of the specific register of the peripheral with `pName`
getPeriphRegFields
  :: String -- ^ Peripheral name
  -> String -- ^ Register name
  -> Device
  -> Either String [Field]
getPeriphRegFields pName rName dev =
  regFields <$> getPeriphReg pName rName dev

getReg
  :: String -- ^ Peripheral name
  -> String -- ^ Register name
  -> Device
  -> Register
getReg pName rName dev =
  Safe.headNote "getReg"
  . filter((==rName) . regName)
  . periphRegisters
  $ getPeriph pName dev

getRegFields
  :: String -- ^ Peripheral name
  -> String -- ^ Register name
  -> Device
  -> [Field]
getRegFields pName rName dev =
  regFields
  $ getReg pName rName dev

-- | Get value of specific @Field@ according to input `x`
getFieldVal :: (Bits a, Num a) => a -> Field -> a
getFieldVal x f = (x `shiftR` fieldBitOffset f) .&. (2 ^ fieldBitWidth f - 1)

-- | Decode integer `x` according to Fields `fs`
getFieldValues :: (Bits a, Num a) => a -> [Field] -> [(a, Field)]
getFieldValues x fs = zip (map (getFieldVal x) fs) fs

-- | Check if any reserved field has value other than 0
anyReservedSet :: (Eq a, Num a) => [(a, Field)] -> Bool
anyReservedSet = any (\(val, f) -> val /= 0 && fieldReserved f)

-- | Get memory map of the device according to its perhiperal addresses
getDevMemMap :: Device -> [(String, String)]
getDevMemMap Device{..} =
  map
    (liftM2 (,) (Data.Bits.Pretty.formatHex . periphBaseAddress) periphName)
    devicePeripherals

registerNames :: String -> Device -> [String]
registerNames pName dev =
  map
    regName . periphRegisters
    $ getPeriph pName dev

fieldNames :: String -> String -> Device -> [String]
fieldNames rName pName dev =
  map
    fieldName
    $ getRegFields pName rName dev

-- * Sorting

-- | Sort everything by memory address
sortDeviceByAddresses :: Device -> Device
sortDeviceByAddresses =
    over
      peripherals
      (Data.List.sortOn (view baseAddress))
  . over
      (peripherals . traverse . registers)
      (Data.List.sortOn (view addressOffset))
  . over
      (peripherals . traverse . registers . traverse . fields)
      (reverse . Data.List.sortOn (view bitOffset))

-- | Sort everything by name
sortDeviceByNames :: Device -> Device
sortDeviceByNames =
    over
      peripherals
      (Data.List.sortOn (view name))
  . over
      (peripherals . traverse . registers)
      (Data.List.sortOn (view name))
  . over
      (peripherals . traverse . registers . traverse . fields)
      (Data.List.sortOn (view name))

-- * Interrupts

fillMissingInterrupts
  :: [Interrupt]
  -> [Interrupt]
fillMissingInterrupts isrs =
  isrs
  ++ (map filler $ missingInterrupts)
  where
    filler x = Interrupt {
       interruptName = "Undefined" ++ show x
     , interruptValue = x
     , interruptDescription = "Undefined interrupt (padding only)"
     }
    missingInterrupts =
      let
        vals = map interruptValue isrs
      in
          Data.Set.toList
        $ Data.Set.difference
            (Data.Set.fromList [0 .. maximum vals])
            (Data.Set.fromList vals)