{-# LINE 1 "src/System/LibCPUID/CPUID.hsc" #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  System.LibCPUID.CPUID
-- Copyright   :  (c) Daniel Taskoff, 2020
-- License     :  MIT
--
-- Maintainer  :  daniel.taskoff@gmail.com
-- Stability   :  experimental
--
-- A wrapper around __cpu_id_t__ from https://github.com/anrieff/libcpuid.
--
-- Usage:
--
-- > cpuid >>= \case
-- >  Left err -> error err
-- >  Right CPUID {..} -> do
-- >    mapM_ putStrLn
-- >      [ "Available CPU information"
-- >      , "------------------------------------------"
-- >      , "vendor string: " ++ vendorString
-- >      , "brand string: " ++ brandString
-- >      , "has a time-stamp counter (TSC): " ++  if hasTSC then "yes" else "no"
-- >      , "# physical cores per processor: " ++ show physicalCores
-- >      , "# logical cores per processor: " ++ show logicalCores
-- >      , "# processors: " ++ show (div totalLogicalCores logicalCores)
-- >      ]
-----------------------------------------------------------------------------

module System.LibCPUID.CPUID
  (
  -- * 'CPUID'
    CPUID(..), cpuid
  , isCPUIDPresent
  ) where

import Foreign.C.String (peekCString)
import Foreign.C.Types (CInt(..), CUChar(..))
import Foreign.Marshal (advancePtr, allocaBytes)
import Foreign.Ptr (Ptr, castPtr, plusPtr)
import Foreign.Storable (Storable(..))


-- | CPU information and features.
data CPUID = CPUID
  { vendorString :: String
    -- ^ CPU vendor string, e.g. \"GenuineIntel\".
  , brandString :: String
    -- ^ CPU brand string, e.g. \"Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz\".
  , hasTSC :: Bool
    -- ^ Is a time-stamp counter available.
  , physicalCores :: Int
    -- ^ Number of physical cores per processor.
  , logicalCores :: Int
    -- ^ Number of logical cores per processor.
  , totalLogicalCores :: Int
    -- ^ Total number of logical cores, which is 'logicalCores' multiplied by the number of processors.
  }



instance Storable CPUID where
  alignment _ = 8
{-# LINE 63 "src/System/LibCPUID/CPUID.hsc" #-}
  sizeOf _ = (432)
{-# LINE 64 "src/System/LibCPUID/CPUID.hsc" #-}
  peek ptr = do
    vendorString <- peekCString $ plusPtr ptr (0)
{-# LINE 66 "src/System/LibCPUID/CPUID.hsc" #-}
    brandString <- peekCString $ plusPtr ptr (16)
{-# LINE 67 "src/System/LibCPUID/CPUID.hsc" #-}
    hasTSC <-
      let ptr' = advancePtr (castPtr @CPUID @CUChar ptr) 4
{-# LINE 69 "src/System/LibCPUID/CPUID.hsc" #-}
       in (/= 0) <$> peekElemOff ptr' (84)
{-# LINE 70 "src/System/LibCPUID/CPUID.hsc" #-}
    physicalCores <- fromIntegral @CInt <$> (\hsc_ptr -> peekByteOff hsc_ptr 232) ptr
{-# LINE 71 "src/System/LibCPUID/CPUID.hsc" #-}
    logicalCores <- fromIntegral @CInt <$> (\hsc_ptr -> peekByteOff hsc_ptr 236) ptr
{-# LINE 72 "src/System/LibCPUID/CPUID.hsc" #-}
    totalLogicalCores <- fromIntegral @CInt <$> (\hsc_ptr -> peekByteOff hsc_ptr 240) ptr
{-# LINE 73 "src/System/LibCPUID/CPUID.hsc" #-}

    pure CPUID {..}
  poke _ _ = error "CPUID is read-only"

-- | Get CPU information and features, or an error message, if the CPU can't be identified by libcpuid.
cpuid :: IO (Either String CPUID)
cpuid = do
  allocaBytes (1344) \cpu_raw_data_t_ptr -> do
{-# LINE 81 "src/System/LibCPUID/CPUID.hsc" #-}
    res <- c_cpuid_get_raw_data cpu_raw_data_t_ptr
    case toMaybeError res of
      Just err -> pure $ Left err
      Nothing ->
        allocaBytes (432) \cpu_id_t_ptr -> do
{-# LINE 86 "src/System/LibCPUID/CPUID.hsc" #-}
          res' <- c_cpu_identify cpu_raw_data_t_ptr cpu_id_t_ptr
          case toMaybeError res' of
            Just err -> pure $ Left err
            Nothing -> Right <$> peek cpu_id_t_ptr

foreign import ccall "cpuid_get_raw_data"
  c_cpuid_get_raw_data :: Ptr cpu_raw_data_t -> IO CInt

foreign import ccall "cpu_identify"
  c_cpu_identify :: Ptr cpu_raw_data_r -> Ptr cpu_id_t -> IO CInt

toMaybeError :: CInt -> Maybe String
toMaybeError = \case
  0 -> Nothing
{-# LINE 100 "src/System/LibCPUID/CPUID.hsc" #-}
  err -> Just
    case err of
      -1 -> "CPUID instruction is not supported"
{-# LINE 103 "src/System/LibCPUID/CPUID.hsc" #-}
      -2 -> "RDTSC instruction is not supported"
{-# LINE 104 "src/System/LibCPUID/CPUID.hsc" #-}
      -3 -> "Memory allocation failed"
{-# LINE 105 "src/System/LibCPUID/CPUID.hsc" #-}
      -4 -> "File open operation failed"
{-# LINE 106 "src/System/LibCPUID/CPUID.hsc" #-}
      -5 -> "Bad file format"
{-# LINE 107 "src/System/LibCPUID/CPUID.hsc" #-}
      -6 -> "Not implemented"
{-# LINE 108 "src/System/LibCPUID/CPUID.hsc" #-}
      -7 -> "Unsupported processor"
{-# LINE 109 "src/System/LibCPUID/CPUID.hsc" #-}
      -8 -> "RDMSR instruction is not supported"
{-# LINE 110 "src/System/LibCPUID/CPUID.hsc" #-}
      -9 -> "RDMSR driver error (generic)"
{-# LINE 111 "src/System/LibCPUID/CPUID.hsc" #-}
      -10 -> "No permissions to install RDMSR driver"
{-# LINE 112 "src/System/LibCPUID/CPUID.hsc" #-}
      -11 -> "Cannot extract RDMSR driver (read only media?)"
{-# LINE 113 "src/System/LibCPUID/CPUID.hsc" #-}
      -12 -> "Bad handle"
{-# LINE 114 "src/System/LibCPUID/CPUID.hsc" #-}
      -13 -> "Invalid MSR"
{-# LINE 115 "src/System/LibCPUID/CPUID.hsc" #-}
      -14 -> "Invalid core number"
{-# LINE 116 "src/System/LibCPUID/CPUID.hsc" #-}
      -15 -> "Error on handle read"
{-# LINE 117 "src/System/LibCPUID/CPUID.hsc" #-}
      -16 -> "Invalid given range"
{-# LINE 118 "src/System/LibCPUID/CPUID.hsc" #-}
      _ -> "Unknown error"

-- | Check if the CPUID instruction is supported.
isCPUIDPresent :: IO Bool
isCPUIDPresent = (== 1) <$> c_cpuid_present

foreign import ccall "cpuid_present"
  c_cpuid_present :: IO CInt