github.com/9elements/firmware-action/action@v0.0.0-20240514065043-044ed91c9ed8/recipes/coreboot.go (about) 1 // SPDX-License-Identifier: MIT 2 3 // Package recipes / coreboot 4 package recipes 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "log/slog" 11 "os" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "strings" 16 17 "dagger.io/dagger" 18 "github.com/9elements/firmware-action/action/container" 19 "github.com/9elements/firmware-action/action/filesystem" 20 "github.com/9elements/firmware-action/action/logging" 21 ) 22 23 // BlobDef is used to store information about a single blob. 24 // This structure is not exposed to the user, it is filled in automatically based on user input. 25 type BlobDef struct { 26 // Path to the blob (either file or directory) 27 Path string `validate:"required"` 28 29 // Blobs get renamed when moved to this string 30 DestinationFilename string `validate:"required"` 31 32 // Kconfig key specifying the filepath to the blob in defconfig 33 KconfigKey string `validate:"required"` 34 35 // Is blob a directory? If blob is file, set to FALSE 36 IsDirectory bool `validate:"required,boolean"` 37 } 38 39 // CorebootBlobs is used to store data specific to coreboot. 40 // ANCHOR: CorebootBlobs 41 type CorebootBlobs struct { 42 // ** List of supported blobs ** 43 // NOTE: The blobs may not be added to the ROM, depends on provided defconfig. 44 // 45 // Gives the (relative) path to the payload. 46 // In a 'coreboot' build, the file will be placed at 47 // `3rdparty/blobs/mainboard/$(MAINBOARDDIR)/payload`. 48 // The Kconfig `CONFIG_PAYLOAD_FILE` will be changed to point to the same path. 49 PayloadFilePath string `json:"payload_file_path" type:"blob"` 50 51 // Gives the (relative) path to the Intel Flash descriptor binary. 52 // In a 'coreboot' build, the file will be placed at 53 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/descriptor.bin`. 54 // The Kconfig `CONFIG_IFD_BIN_PATH` will be changed to point to the same path. 55 IntelIfdPath string `json:"intel_ifd_path" type:"blob"` 56 57 // Gives the (relative) path to the Intel Management engine binary. 58 // In a 'coreboot' build, the file will be placed at 59 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/me.bin`. 60 // The Kconfig `CONFIG_ME_BIN_PATH` will be changed to point to the same path. 61 IntelMePath string `json:"intel_me_path" type:"blob"` 62 63 // Gives the (relative) path to the Intel Gigabit Ethernet engine binary. 64 // In a 'coreboot' build, the file will be placed at 65 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/gbe.bin`. 66 // The Kconfig `CONFIG_GBE_BIN_PATH` will be changed to point to the same path. 67 IntelGbePath string `json:"intel_gbe_path" type:"blob"` 68 69 // Gives the (relative) path to the Intel FSP binary. 70 // In a 'coreboot' build, the file will be placed at 71 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/Fsp.fd`. 72 // The Kconfig `CONFIG_FSP_FD_PATH` will be changed to point to the same path. 73 FspBinaryPath string `json:"fsp_binary_path" type:"blob"` 74 75 // Gives the (relative) path to the Intel FSP header folder. 76 // In a 'coreboot' build, the files will be placed at 77 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/Include`. 78 // The Kconfig `CONFIG_FSP_HEADER_PATH` will be changed to point to the same path. 79 FspHeaderPath string `json:"fsp_header_path" type:"blob"` 80 81 // Gives the (relative) path to the Video BIOS Table binary. 82 // In a 'coreboot' build, the files will be placed at 83 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/vbt.bin`. 84 // The Kconfig `CONFIG_INTEL_GMA_VBT_FILE` will be changed to point to the same path. 85 VbtPath string `json:"vbt_path" type:"blob"` 86 87 // Gives the (relative) path to the Embedded Controller binary. 88 // In a 'coreboot' build, the files will be placed at 89 // `3rdparty/blobs/mainboard/$(CONFIG_MAINBOARD_DIR)/ec.bin`. 90 // The Kconfig `CONFIG_EC_BIN_PATH` will be changed to point to the same path. 91 EcPath string `json:"ec_path" type:"blob"` 92 } 93 94 // ANCHOR_END: CorebootBlobs 95 96 // CorebootOpts is used to store all data needed to build coreboot. 97 // ANCHOR: CorebootOpts 98 type CorebootOpts struct { 99 // List of IDs this instance depends on 100 Depends []string `json:"depends"` 101 102 // Common options like paths etc. 103 CommonOpts 104 105 // Gives the (relative) path to the defconfig that should be used to build the target. 106 DefconfigPath string `json:"defconfig_path" validate:"required,filepath"` 107 108 // Coreboot specific options 109 Blobs CorebootBlobs `json:"blobs"` 110 } 111 112 // ANCHOR_END: CorebootOpts 113 114 // GetDepends is used to return list of dependencies 115 func (opts CorebootOpts) GetDepends() []string { 116 return opts.Depends 117 } 118 119 // GetArtifacts returns list of wanted artifacts from container 120 func (opts CorebootOpts) GetArtifacts() *[]container.Artifacts { 121 return opts.CommonOpts.GetArtifacts() 122 } 123 124 // corebootProcessBlobs is used to fill figure out blobs from provided data. 125 func corebootProcessBlobs(opts CorebootBlobs) ([]BlobDef, error) { 126 blobMap := map[string]BlobDef{ 127 // Payload 128 // docs: https://doc.coreboot.org/payloads.html 129 "payload_file_path": { 130 DestinationFilename: "payload", 131 KconfigKey: "CONFIG_PAYLOAD_FILE", 132 IsDirectory: false, 133 }, 134 // Intel IFD (Intel Flash Descriptor) 135 // docs: https://doc.coreboot.org/util/ifdtool/layout.html 136 "intel_ifd_path": { 137 DestinationFilename: "descriptor.bin", 138 KconfigKey: "CONFIG_IFD_BIN_PATH", 139 IsDirectory: false, 140 }, 141 // Intel ME (Intel Management Engine) 142 "intel_me_path": { 143 DestinationFilename: "me.bin", 144 KconfigKey: "CONFIG_ME_BIN_PATH", 145 IsDirectory: false, 146 }, 147 // Intel GbE (Intel Gigabit Ethernet) 148 "intel_gbe_path": { 149 DestinationFilename: "gbe.bin", 150 KconfigKey: "CONFIG_GBE_BIN_PATH", 151 IsDirectory: false, 152 }, 153 // Intel FSP binary (Intel Firmware Support Package) 154 "fsp_binary_path": { 155 DestinationFilename: "Fsp.fd", 156 KconfigKey: "CONFIG_FSP_FD_PATH", 157 IsDirectory: false, 158 }, 159 // Intel FSP header (Intel Firmware Support Package) 160 "fsp_header_path": { 161 DestinationFilename: "Include", 162 KconfigKey: "CONFIG_FSP_HEADER_PATH", 163 IsDirectory: true, 164 }, 165 // VBT (Video BIOS Table) 166 "vbt_path": { 167 DestinationFilename: "vbt.bin", 168 KconfigKey: "CONFIG_INTEL_GMA_VBT_FILE", 169 IsDirectory: false, 170 }, 171 // EC (Embedded Controller) 172 "ec_path": { 173 DestinationFilename: "ec.bin", 174 KconfigKey: "CONFIG_EC_BIN_PATH", 175 IsDirectory: false, 176 }, 177 } 178 blobs := []BlobDef{} 179 180 blob := reflect.ValueOf(opts) 181 for i := 0; i < blob.Type().NumField(); i++ { 182 t := blob.Type().Field(i) 183 184 jsonTag := t.Tag.Get("json") 185 jsonType := t.Tag.Get("type") 186 if jsonTag != "" && jsonType == "blob" { 187 newBlob := blobMap[jsonTag] 188 newBlob.Path = blob.Field(i).Interface().(string) 189 if newBlob.Path != "" { 190 blobs = append(blobs, newBlob) 191 } 192 } 193 } 194 return blobs, nil 195 } 196 197 // buildFirmware builds coreboot with all blobs and stuff 198 func (opts CorebootOpts) buildFirmware(ctx context.Context, client *dagger.Client, dockerfileDirectoryPath string) (*dagger.Container, error) { 199 // Spin up container 200 containerOpts := container.SetupOpts{ 201 ContainerURL: opts.SdkURL, 202 MountContainerDir: ContainerWorkDir, 203 MountHostDir: opts.RepoPath, 204 WorkdirContainer: ContainerWorkDir, 205 } 206 myContainer, err := container.Setup(ctx, client, &containerOpts, dockerfileDirectoryPath) 207 if err != nil { 208 slog.Error( 209 "Failed to start a container", 210 slog.Any("error", err), 211 ) 212 return nil, err 213 } 214 215 // Copy over the defconfig file 216 defconfigBasename := filepath.Base(opts.DefconfigPath) 217 // not sure why, but without the 'pwd' I am getting different results between CI and 'go test' 218 pwd, err := os.Getwd() 219 if err != nil { 220 slog.Error( 221 "Could not get working directory, should not happen", 222 slog.String("suggestion", logging.ThisShouldNotHappenMessage), 223 slog.Any("error", err), 224 ) 225 return nil, err 226 } 227 myContainer = myContainer.WithFile( 228 filepath.Join(ContainerWorkDir, defconfigBasename), 229 client.Host().File(filepath.Join(pwd, opts.DefconfigPath)), 230 ) 231 232 // Get value of CONFIG_MAINBOARD_DIR / MAINBOARD_DIR variable from dotconfig 233 // to extract value of 'CONFIG_MAINBOARD_DIR', there must be '.config' 234 generateDotConfigCmd := []string{"make", fmt.Sprintf("KBUILD_DEFCONFIG=%s", defconfigBasename), "defconfig"} 235 myContainerPrevious := myContainer 236 mainboardDir, err := myContainer. 237 WithExec(generateDotConfigCmd). 238 WithExec([]string{"./util/scripts/config", "-s", "CONFIG_MAINBOARD_DIR"}). 239 Stdout(ctx) 240 if err != nil { 241 slog.Error( 242 "Failed to get value of MAINBOARD_DIR from .config", 243 slog.Any("error", err), 244 ) 245 return myContainerPrevious, err 246 } 247 // strip newline from mainboardDir 248 mainboardDir = strings.Replace(mainboardDir, "\n", "", -1) 249 250 // Assemble commands to build 251 buildSteps := [][]string{ 252 // remove existing config if exists 253 // -f: ignore nonexistent files 254 {"rm", "-f", ".config"}, 255 // generate dotconfig from defconfig 256 generateDotConfigCmd, 257 } 258 259 // Handle blobs 260 // Firstly copy all the blobs into building container. 261 // Then use './util/scripts/config' script in coreboot repository to update configuration 262 // options for said blobs (this must run inside container). 263 blobs, err := corebootProcessBlobs(opts.Blobs) 264 if err != nil { 265 slog.Error( 266 "Failed to process all blobs", 267 slog.Any("error", err), 268 ) 269 return nil, err 270 } 271 for blob := range blobs { 272 // Path to local file on host 273 src := filepath.Join( 274 pwd, 275 blobs[blob].Path, 276 ) 277 // Path to file in container 278 dst := filepath.Join( 279 filepath.Join("3rdparty/blobs/mainboard", mainboardDir), 280 blobs[blob].DestinationFilename, 281 ) 282 283 // Copy into container 284 if err = filesystem.CheckFileExists(src); !errors.Is(err, os.ErrExist) { 285 return nil, err 286 } 287 if blobs[blob].IsDirectory { 288 // Directory 289 slog.Info(fmt.Sprintf("Copying directory '%s' to container at '%s'", src, dst)) 290 myContainer = myContainer.WithExec([]string{"mkdir", "-p", dst}) 291 // myContainer = myContainer.WithMountedDirectory( 292 // can't use WithMountedDirectory because the repo (aka working directory) 293 // is already mounted with WithMountedDirectory 294 // this nesting causes problems 295 myContainer = myContainer.WithDirectory( 296 dst, 297 client.Host().Directory(src), 298 ) 299 } else { 300 // File 301 myContainer = myContainer.WithFile( 302 dst, 303 client.Host().File(src), 304 ) 305 } 306 307 // Fix defconfig 308 buildSteps = append( 309 buildSteps, 310 // update coreboot config value related to blob to actual path of the blob 311 []string{"./util/scripts/config", "--set-str", blobs[blob].KconfigKey, dst}, 312 ) 313 } 314 315 buildSteps = append( 316 buildSteps, 317 // compile 318 []string{"make", "-j", fmt.Sprintf("%d", runtime.NumCPU())}, 319 // for documenting purposes 320 []string{"make", "savedefconfig"}, 321 ) 322 323 // Setup environment variables in the container 324 envVars := map[string]string{} 325 for key, value := range envVars { 326 myContainer = myContainer.WithEnvVariable(key, value) 327 } 328 329 // Build 330 for step := range buildSteps { 331 myContainerPrevious := myContainer 332 myContainer, err = myContainer. 333 WithExec(buildSteps[step]). 334 Sync(ctx) 335 if err != nil { 336 slog.Error( 337 "Failed to build coreboot", 338 slog.Any("error", err), 339 ) 340 return myContainerPrevious, fmt.Errorf("coreboot build failed: %w", err) 341 } 342 } 343 344 // Extract artifacts 345 return myContainer, container.GetArtifacts(ctx, myContainer, opts.CommonOpts.GetArtifacts()) 346 }