├── .rspec ├── .yardopts ├── FELCmds.rb ├── FELConsts.rb ├── FELHelpers.rb ├── FELStructs.rb ├── FELSuit.rb ├── Gemfile ├── README.md ├── felix ├── libsparse.rb └── spec ├── assets ├── bootloader.sample ├── env.sample ├── sparse1.sample ├── sys_config.sample ├── sys_config1.sample ├── sys_para.sample └── u-boot.sample ├── felix_commands_spec.rb ├── felix_helpers_spec.rb ├── felix_struct_spec.rb ├── libsparse_spec.rb └── spec_helper.rb /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format doc 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title 'FELix documentation' 2 | --markup markdown 3 | --charset utf-8 4 | 'FEL*.rb' 5 | felix 6 | libsparse.rb 7 | -------------------------------------------------------------------------------- /FELCmds.rb: -------------------------------------------------------------------------------- 1 | # FELCmds.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | raise "Use ./felix to execute program!" if File.basename($0) == File.basename(__FILE__) 18 | 19 | class FELix 20 | 21 | # Send a request 22 | # @param data the binary data 23 | # @param method [Symbol] a method of transfer (`:v1` or `:v2`) 24 | # @raise [FELError, FELFatal] 25 | def send_request(data, method = :v1) 26 | # 1. Send AWUSBRequest to inform what we want to do (write/read/how many data) 27 | if method == :v1 28 | request = AWUSBRequest.new 29 | request.len = data.bytesize 30 | FELHelpers.debug_packet(request.to_binary_s, :write) if $options[:verbose] 31 | @handle.bulk_transfer(:dataOut => request.to_binary_s, :endpoint => 32 | @usb_out, :timeout=>(5 * 1000)) 33 | end 34 | # 2. Send a proper data 35 | FELHelpers.debug_packet(data, :write) if $options[:verbose] 36 | @handle.bulk_transfer(:dataOut => data, :endpoint => @usb_out, :timeout=>(5 * 1000)) 37 | # 3. Get AWUSBResponse 38 | if method == :v1 39 | # Some request takes a lot of time (i.e. NAND format). Try to wait 60 seconds for response. 40 | r3 = @handle.bulk_transfer(:dataIn => 13, :endpoint => @usb_in, :timeout=>(60 * 1000)) 41 | FELHelpers.debug_packet(r3, :read) if $options[:verbose] 42 | end 43 | rescue LIBUSB::ERROR_INTERRUPTED, LIBUSB::ERROR_TIMEOUT 44 | raise FELError, "Transfer cancelled" 45 | rescue => e 46 | raise FELFatal, "Failed to send ".red << "#{data.bytesize}".yellow << " bytes".red << 47 | " (" << e.message << ")" 48 | end 49 | 50 | # Read data 51 | # @param len an expected length of the data 52 | # @param method [Symbol] a method of transfer (`:v1` or `:v2`) 53 | # @return [String] the binary data 54 | # @raise [FELError, FELFatal] 55 | def recv_request(len, method = :v1) 56 | # 1. Send AWUSBRequest to inform what we want to do (write/read/how many data) 57 | if method == :v1 58 | request = AWUSBRequest.new 59 | request.len = len 60 | request.cmd = USBCmd[:read] 61 | FELHelpers.debug_packet(request.to_binary_s, :write) if $options[:verbose] 62 | @handle.bulk_transfer(:dataOut => request.to_binary_s, :endpoint => @usb_out) 63 | end 64 | # 2. Read data of length we specified in request 65 | recv_data = @handle.bulk_transfer(:dataIn => len, :endpoint => @usb_in) 66 | FELHelpers.debug_packet(recv_data, :read) if $options[:verbose] 67 | # 3. Get AWUSBResponse 68 | if method == :v1 69 | response = @handle.bulk_transfer(:dataIn => 13, :endpoint => @usb_in) 70 | FELHelpers.debug_packet(response, :read) if $options[:verbose] 71 | end 72 | recv_data 73 | rescue LIBUSB::ERROR_INTERRUPTED, LIBUSB::ERROR_TIMEOUT 74 | raise FELError, "Transfer cancelled" 75 | rescue => e 76 | raise FELFatal, "Failed to receive ".red << "#{len}".yellow << " bytes".red << 77 | " (" << e.message << ")" 78 | end 79 | 80 | # An unified higher level function for PC<->device communication 81 | # 82 | # @param direction [:push, :pull] a method of transfer 83 | # @param request [AWFELMessage, AWFELStandardRequest] a request 84 | # @param size [Integer] an expected size of an answer (for `:pull`) 85 | # @param data [String] the binary data (for `:push`) 86 | # @param method [Symbol] a method of transfer (`:v1` or `:v2`) 87 | # @return [String, nil] the received answer (for `:pull`) 88 | # @raise [FELError, FELFatal] 89 | def transfer(direction, request, size:nil, data:nil, method: :v1) 90 | raise FELFatal, "\nAn invalid argument for :pull" if data && direction == :pull 91 | raise FELFatal, "\nAn invalid argument for :push" if size && direction == :push 92 | raise FELFatal, "\nAn invalid direction: #{direction}" unless [:pull, :push]. 93 | include? direction 94 | raise FELFatal, "\nAn invalid transfer method #{method}" unless [:v1, :v2]. 95 | include? method 96 | 97 | begin 98 | send_request(request.to_binary_s, method) 99 | rescue FELError 100 | retry 101 | rescue FELFatal => e 102 | raise FELFatal, "\nFailed to send a request: #{e}" 103 | end 104 | 105 | answer = nil 106 | 107 | case direction 108 | when :pull 109 | begin 110 | answer = recv_request(size, method) 111 | raise FELFatal, "\nAn unexpected answer length (#{answer.length} <>" << 112 | " #{size})" if answer.length!= size 113 | rescue FELFatal => e 114 | raise FELFatal, "\nFailed to get the data (#{e})" 115 | end if size > 0 116 | when :push 117 | begin 118 | send_request(data, method) 119 | rescue FELError 120 | retry 121 | rescue FELFatal => e 122 | raise FELFatal, "\nFailed to send the data: #{e}" 123 | end if data 124 | end 125 | 126 | begin 127 | case method 128 | when :v1 129 | data = recv_request(8, method) 130 | status = AWFELStatusResponse.read(data) 131 | raise FELError, "\nCommand execution failed (Status #{status.state})" if 132 | status.state > 0 133 | when :v2 134 | data = recv_request(13, method) 135 | status = AWUSBResponse.read(data) 136 | raise FELError, "\nCommand execution failed (Status #{status.csw_status})" if 137 | status.csw_status != 0 138 | end 139 | rescue BinData::ValidityError => e 140 | raise FELFatal, "\nAn unexpected device response: #{e}" 141 | rescue FELFatal => e 142 | raise FELFatal, "\nFailed to receive the device status: #{e}" 143 | end 144 | 145 | answer if direction == :pull 146 | end 147 | 148 | # Get the device status 149 | # @return [AWFELVerifyDeviceResponse] a status of the device 150 | # @raise [FELError, FELFatal] 151 | def get_device_status 152 | answer = transfer(:pull, AWFELStandardRequest.new, size: 32) 153 | #answer = transfer(:pull, AWUSBRequestV2.new, size: 32, method: :v2) 154 | AWFELVerifyDeviceResponse.read(answer) 155 | end 156 | 157 | # Read memory from device 158 | # @param address [Integer] a memory address to read from 159 | # @param length [Integer] a size of the read memory 160 | # @param tags [Symbol, Array] an operation tag (zero or more of AWTags) 161 | # @param mode [AWDeviceMode] an operation mode `:fel` or `:fes` 162 | # @return [String] the requested memory block if block has only one parameter 163 | # @raise [FELError, FELFatal] 164 | # @yieldparam [Integer] bytes read as far 165 | # @yieldparam [String] read data chunk of `FELIX_MAX_CHUNK` 166 | # @note Not usable on the legacy fes (boot1.0) 167 | def read(address, length, tags=[:none], mode=:fel, &block) 168 | raise FELError, "The length is not specifed" unless length 169 | raise FELError, "The address is not specifed" unless address 170 | result = "" 171 | remain_len = length 172 | request = AWFELMessage.new 173 | request.cmd = mode == :fel ? FELCmd[:upload] : FESCmd[:upload] 174 | request.address = address.to_i 175 | if tags.kind_of?(Array) 176 | tags.each {|t| request.flags |= AWTags[t]} 177 | else 178 | request.flags |= AWTags[tags] 179 | end 180 | 181 | while remain_len>0 182 | if remain_len / FELIX_MAX_CHUNK == 0 183 | request.len = remain_len 184 | else 185 | request.len = FELIX_MAX_CHUNK 186 | end 187 | 188 | data = transfer(:pull, request, size: request.len) 189 | result << data if block.arity < 2 190 | remain_len-=request.len 191 | 192 | # if EFEX_TAG_DRAM isnt set we read nand/sdcard 193 | if request.flags & AWTags[:dram] == 0 && mode == :fes 194 | next_sector=(request.len / 512).to_i 195 | request.address+=( next_sector ? next_sector : 1) # Read next sector if its less than 512 196 | else 197 | request.address+=request.len.to_i 198 | end 199 | yield length-remain_len, data if block_given? 200 | end 201 | result 202 | end 203 | 204 | # Write data to device memory 205 | # @param address [Integer] a place in memory to write 206 | # @param memory [String] data to write 207 | # @param tags [Symbol, Array] an operation tag (zero or more of AWTags) 208 | # @param mode [AWDeviceMode] an operation mode `:fel` or `:fes` 209 | # @param dontfinish do not set finish tag in `:fes` context 210 | # @param method [Symbol] communication method (`:v1` or `:v2`) 211 | # @raise [FELError, FELFatal] 212 | # @yieldparam [Integer] bytes written as far 213 | # @note Not usable on the legacy fes (boot1.0) 214 | def write(address, memory, tags=[:none], mode=:fel, dontfinish=false, method: :v1) 215 | raise FELError, "The memory is not specifed" unless memory 216 | raise FELError, "The address is not specifed" unless address 217 | 218 | total_len = memory.bytesize 219 | start = 0 220 | request = method == :v1 ? AWFELMessage.new : AWUSBRequestV2.new 221 | request.cmd = mode == :fel ? FELCmd[:download] : FESCmd[:download] 222 | request.address = address.to_i 223 | if tags.kind_of?(Array) 224 | tags.each {|t| request.flags |= AWTags[t]} 225 | else 226 | request.flags |= AWTags[tags] 227 | end 228 | 229 | while total_len>0 230 | if total_len / FELIX_MAX_CHUNK == 0 231 | request.len = total_len 232 | else 233 | request.len = FELIX_MAX_CHUNK 234 | end 235 | # At last chunk finish tag must be set 236 | request.flags |= AWTags[:finish] if mode == :fes && 237 | total_len <= FELIX_MAX_CHUNK && dontfinish == false 238 | 239 | transfer(:push, request, data: memory.byteslice(start, request.len), method: method) 240 | 241 | start+=request.len 242 | total_len-=request.len 243 | # if EFEX_TAG_DRAM isnt set we write nand/sdcard 244 | if request.flags & AWTags[:dram] == 0 && mode == :fes 245 | next_sector = (request.len / 512).to_i 246 | request.address+=( next_sector ? next_sector : 1) # Write next sector if its less than 512 247 | else 248 | request.address+=request.len.to_i 249 | end 250 | yield start if block_given? # yield sent bytes 251 | end 252 | end 253 | 254 | # Execute code at specified memory 255 | # @param address [Integer] a memory address to read from 256 | # @param mode [AWDeviceMode] an operation mode `:fel` or `:fes` 257 | # @param flags [Array] zero or more flags (in `:fes` only) 258 | # @param args [Array] an array of arguments if `:has_param` flag is set 259 | # @param method [Symbol] communication method (`:v1` or `:v2`) 260 | # @note `flags` is `max_para` in boot2.0 261 | # @raise [FELError, FELFatal] 262 | def run(address, mode=:fel, flags = :none, args = nil, method: :v1) 263 | request = method == :v1 ? AWFELMessage.new : AWUSBRequestV2.new 264 | request.cmd = mode == :fel ? FELCmd[:run] : FESCmd[:run] 265 | request.address = address 266 | 267 | raise FELFatal, "\nCannot use flags in FEL" if flags != :none && mode == :fel 268 | if flags.kind_of?(Array) 269 | flags.each do |f| 270 | raise FELFatal, "\nAn unknown flag #{f}" unless AWRunContext.has_key? f 271 | request.len |= AWRunContext[f] 272 | end 273 | else 274 | raise FELFatal, "\nAn unknown flag #{f}" unless AWRunContext.has_key? flags 275 | request.len |= AWRunContext[flags] 276 | end 277 | if request.len & AWRunContext[:has_param] == AWRunContext[:has_param] 278 | params = AWFESRunArgs.new(:args => args) 279 | transfer(:push, request, data:params.to_binary_s) 280 | else 281 | transfer(:push, request) 282 | end 283 | end 284 | 285 | # Send a FES_INFO request (get the code execution status) 286 | # @raise [FELError, FELFatal] 287 | # @return [String] the device response (32 bytes) 288 | def info 289 | request = AWFELMessage.new 290 | request.cmd = FESCmd[:info] 291 | transfer(:pull, request, size: 32) 292 | end 293 | 294 | # Send a FES_GET_MSG request (get the code execution status string) 295 | # @param len [Integer] a length of requested data 296 | # @raise [FELError, FELFatal] 297 | # @return [String] the device response (default 1024 bytes) 298 | def get_msg(len = 1024) 299 | request = AWFELMessage.new 300 | request.cmd = FESCmd[:get_msg] 301 | request.address = len 302 | transfer(:pull, request, size: len) 303 | end 304 | 305 | # Send a FES_UNREG_FED request (detach the storage) 306 | # @param type [FESIndex] a storage type 307 | # @raise [FELError, FELFatal] 308 | def unreg_fed(type = :nand) 309 | request = AWFELMessage.new 310 | request.cmd = FESCmd[:unreg_fed] 311 | request.address = FESIndex[type] 312 | transfer(:push, request) 313 | end 314 | 315 | # Verify the last operation status 316 | # @param tags [Symbol, Array] an operation tag (zero or more of AWTags) 317 | # @return [AWFESVerifyStatusResponse] the device status 318 | # @raise [FELError, FELFatal] 319 | # @note Use only in a :fes mode 320 | def verify_status(tags=[:none]) 321 | request = AWFELMessage.new 322 | request.cmd = FESCmd[:verify_status] 323 | if tags.kind_of?(Array) 324 | tags.each {|t| request.flags |= AWTags[t]} 325 | else 326 | request.flags |= AWTags[tags] 327 | end 328 | # Verification finish flag may be not set immediately 329 | 5.times do 330 | answer = transfer(:pull, request, size: 12) 331 | resp = AWFESVerifyStatusResponse.read(answer) 332 | return resp if resp.flags == 0x6a617603 333 | sleep(300) 334 | end 335 | raise FELError, "The verify process has timed out" 336 | end 337 | 338 | # Verify the checksum of the given memory block 339 | # @param address [Integer] a memory address 340 | # @param len [Integer] a length of verfied block 341 | # @return [AWFESVerifyStatusResponse] the verification response 342 | # @raise [FELError, FELFatal] 343 | # @note Use only in a :fes mode 344 | def verify_value(address, len) 345 | request = AWFELMessage.new 346 | request.cmd = FESCmd[:verify_value] 347 | request.address = address 348 | request.len = len 349 | 350 | answer = transfer(:pull, request, size: 12) 351 | AWFESVerifyStatusResponse.read(answer) 352 | end 353 | 354 | # Attach / detach the storage (handles `:flash_set_on` and `:flash_set_off`) 355 | # @param how [Symbol] a desired state of the storage (`:on` or `:off`) 356 | # @param type [Integer] type of storage. Unused. 357 | # @raise [FELError, FELFatal] 358 | # @note Use only in a :fes mode. An MBR must be written before 359 | def set_storage_state(how, type=0) 360 | raise FELError, "An invalid parameter state (#{how})" unless [:on, :off]. 361 | include? how 362 | request = AWFELMessage.new 363 | request.cmd = how == :on ? FESCmd[:flash_set_on] : FESCmd[:flash_set_off] 364 | request.address = type # the address field is used for storage type 365 | transfer(:push, request) 366 | end 367 | 368 | # Get security system status. (handles `:query_secure`) 369 | # Secure flag is controlled by `secure_bit` in sys_config.fex 370 | # See more: https://github.com/allwinner-zh/bootloader/blob/master/u-boot-2011.09/arch/arm/cpu/armv7/sun8iw7/board.c#L300 371 | # 372 | # @raise [FELError, FELFatal] 373 | # @return [Symbol] a status flag 374 | # @note Use only in a :fes mode 375 | def query_secure 376 | request = AWFELStandardRequest.new 377 | request.cmd = FESCmd[:query_secure] 378 | 379 | status = transfer(:pull, request, size: 4).unpack("V")[0] 380 | 381 | if AWSecureStatusMode.has_value? status 382 | AWSecureStatusMode.keys[status] 383 | else 384 | AWSecureStatusMode.keys[-1] 385 | end 386 | end 387 | 388 | # Get currently default storage. (handles `:query_storage`) 389 | # Used to determine which boot0 should be written 390 | # 391 | # @raise [FELError, FELFatal] 392 | # @return [Symbol] a status flag or `:unknown` 393 | # @note Use only in a :fes mode 394 | def query_storage 395 | request = AWFELStandardRequest.new 396 | request.cmd = FESCmd[:query_storage] 397 | 398 | status = transfer(:pull, request, size: 4).unpack("V")[0] 399 | 400 | if AWStorageType.has_value? status 401 | AWStorageType.keys[status] 402 | else 403 | :unknown 404 | end 405 | end 406 | 407 | 408 | # Send a FES_TRANSMIT request 409 | # Can be used to read/write memory in FES mode 410 | # 411 | # @param direction [Symbol] one of FESTransmiteFlag (`:write` or `:read`) 412 | # @param opts [Hash] Arguments 413 | # @option opts :address [Integer] place in memory to transmit 414 | # @option opts :memory [String] data to write (use only with `:write`) 415 | # @option opts :media_index [Symbol] one of index (default `:dram`) 416 | # @option opts :length [Integer] size of data (use only with `:read`) 417 | # @option opts :dontfinish [TrueClass, FalseClass] do not set finish tag 418 | # @raise [FELError, FELFatal] 419 | # @return [String] the data if `direction` is `:read` 420 | # @yieldparam [Integer] read/written bytes 421 | # @note Use only in a :fes mode. Always prefer FES_DOWNLOAD/FES_UPLOAD instead of this in boot 2.0 422 | # @TODO: Replace opts -> named arguments 423 | def transmit(direction, *opts) 424 | opts = opts.first 425 | opts[:media_index] ||= :dram 426 | start = 0 427 | if direction == :write 428 | raise FELError, "The memory is not specifed" unless opts[:memory] 429 | raise FELError, "The address is not specifed" unless opts[:address] 430 | 431 | total_len = opts[:memory].bytesize 432 | address = opts[:address] 433 | 434 | request = AWFESTrasportRequest.new # Little optimization 435 | request.flags = FESTransmiteFlag[direction] 436 | request.media_index = FESIndex[opts[:media_index]] 437 | # Set :start tag when writing to physical memory 438 | request.flags |= FESTransmiteFlag[:start] if request.media_index>0 439 | 440 | while total_len>0 441 | request.address = address.to_i 442 | if total_len / FELIX_MAX_CHUNK == 0 443 | request.len = total_len 444 | else 445 | request.len = FELIX_MAX_CHUNK 446 | end 447 | 448 | # At last chunk finish tag must be set 449 | request.flags |= FESTransmiteFlag[:finish] if total_len <= FELIX_MAX_CHUNK && opts[:dontfinish] == false 450 | 451 | transfer(:push, request, data: opts[:memory].byteslice(start, request.len)) 452 | 453 | start+=request.len 454 | total_len-=request.len 455 | if opts[:media_index] == :dram 456 | address+=request.len 457 | else 458 | next_sector=request.len / 512 459 | address+=( next_sector ? next_sector : 1) # Write next sector if its less than 512 460 | end 461 | yield start if block_given? # yield sent bytes 462 | end 463 | elsif direction == :read 464 | raise FELError, "The length is not specifed" unless opts[:length] 465 | raise FELError, "The address is not specifed" unless opts[:address] 466 | result = "" 467 | request = AWFESTrasportRequest.new 468 | address = opts[:address] 469 | request.len = remain_len = length = opts[:length] 470 | request.flags = FESTransmiteFlag[direction] 471 | request.media_index = FESIndex[opts[:media_index]] 472 | 473 | while remain_len>0 474 | request.address = address.to_i 475 | if remain_len / FELIX_MAX_CHUNK == 0 476 | request.len = remain_len 477 | else 478 | request.len = FELIX_MAX_CHUNK 479 | end 480 | 481 | result << transfer(:pull, request, size: request.len) 482 | 483 | remain_len-=request.len 484 | # if EFEX_TAG_DRAM isnt set we read nand/sdcard 485 | if opts[:media_index] == :dram 486 | address+=request.len 487 | else 488 | next_sector=request.len / 512 489 | address+=( next_sector ? next_sector : 1) # Read next sector if its less than 512 490 | end 491 | yield length-remain_len if block_given? 492 | end 493 | result 494 | else 495 | raise FELError, "An unknown direction '(#{direction})'" 496 | end 497 | end 498 | 499 | # Send a FES_SET_TOOL_MODE request 500 | # Can be used to change the device state (i.e. reboot) 501 | # or to change the u-boot work mode 502 | # 503 | # @param mode [Symbol] a mode 504 | # @param action [Symbol] an action. 505 | # if the `action` is `:none` and the `mode` is not `:usb_tool_update` then 506 | # the action is fetched from a sys_config's platform->next_work key. 507 | # If the key does not exist then defaults to `:normal` 508 | # @raise [FELError, FELFatal] 509 | # @note Use only in a :fes mode 510 | # @note Action parameter is respected only if the `mode` is `:usb_tool_update` 511 | # @note This command is only usable for reboot 512 | def set_tool_mode(mode, action=:none) 513 | request = AWFELMessage.new 514 | request.cmd = FESCmd[:tool_mode] 515 | request.address = AWUBootWorkMode[mode] 516 | request.len = AWActions[action] 517 | 518 | transfer(:push, request) 519 | end 520 | 521 | # Write an MBR to the storage (and do format) 522 | # 523 | # @param mbr [String] a new mbr. Must have 65536 bytes of length 524 | # @param format [Boolean] force data wipe 525 | # @return [AWFESVerifyStatusResponse] the result of sunxi_sprite_download_mbr (crc:-1 if fail) 526 | # @raise [FELError, FELFatal] 527 | # @note Use only in a :fes mode 528 | # @note **Warning**: The device may do format anyway if the storage version doesn't match! 529 | def write_mbr(mbr, format=false) 530 | raise FELError, "\nThe MBR is empty!" if mbr.empty? 531 | raise FELError, "\nThe MBR is too small" unless mbr.bytesize == 65536 532 | # 1. Force platform->erase_flag => 1 or 0 if we dont wanna erase 533 | write(0, format ? "\1\0\0\0" : "\0\0\0\0", [:erase, :finish], :fes) 534 | # 2. Verify status (actually this is unecessary step [last_err is not set at all]) 535 | # verify_status(:erase) 536 | # 3. Write MBR 537 | write(0, mbr, [:mbr, :finish], :fes) 538 | # 4. Get result value of sunxi_sprite_verify_mbr 539 | verify_status(:mbr) 540 | end 541 | 542 | end 543 | -------------------------------------------------------------------------------- /FELConsts.rb: -------------------------------------------------------------------------------- 1 | # FELConsts.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | raise "Use ./felix to execute program!" if File.basename($0) == File.basename(__FILE__) 18 | 19 | # App version 20 | FELIX_VERSION = "1.0 RC6" 21 | 22 | # Maximum data transfer length 23 | FELIX_MAX_CHUNK = 65536 24 | # NAND sector size 25 | FELIX_SECTOR = 512 26 | # Image header string 27 | FELIX_IMG_HEADER = "IMAGEWTY" 28 | 29 | # RC6 keys 30 | RC6Keys = { 31 | :header => RC6.new("\0" * 31 << "i"), 32 | :item => RC6.new("\1" * 31 << "m"), 33 | :data => RC6.new("\2" * 31 << "g") 34 | } 35 | 36 | # Error exit code - everything OK 37 | FELIX_SUCCESS = 0 38 | # Error exit code - something wrong happened 39 | FELIX_FAIL = 1 40 | # Error exit code - unhandled erorrs (WTF) 41 | FELIX_FATAL = 2 42 | 43 | # Uboot checksum constant 44 | UBOOT_STAMP_VALUE = 0x5F0A6C39 45 | 46 | #AWUSBRequest type 47 | USBCmd = { 48 | :read => 0x11, 49 | :write => 0x12 50 | } 51 | 52 | #FEL Messages 53 | FELCmd = { 54 | :verify_device => 0x1, # [@get_device_status] can be used in FES 55 | :switch_role => 0x2, 56 | :is_ready => 0x3, # Read len 8, can be used in FES 57 | :get_cmd_set_ver => 0x4, # can be used in FES, may be used to check what commands are available (16 bytes) 58 | :disconnect => 0x10, 59 | :download => 0x101, # [@write] write 60 | :run => 0x102, # [@run] execute 61 | :upload => 0x103 # [@read] read 62 | } 63 | 64 | #FES Messages 65 | FESCmd = { 66 | :transmit => 0x201, # [@transmit] read,write depends on flag, do not use 67 | :run => 0x202, # [@run] 68 | :info => 0x203, # [@info] get if FES_RUN has finished (32 bytes) 69 | :get_msg => 0x204, # [@get_msg] get result of last FES_RUN (param buffer size) 70 | :unreg_fed => 0x205, # [@unreg_fed] unmount NAND/MMC 71 | # Following are available on boot2.0 72 | :download => 0x206, # [@write] 73 | :upload => 0x207, # [@read] 74 | :verify => 0x208, # check CRC of given memory block, not implemented 75 | :query_storage => 0x209, # [@query_storage] used to check if we boot from nand or sdcard 76 | :flash_set_on => 0x20A, # [@set_storage_state] exec sunxi_sprite_init(0) => no data 77 | :flash_set_off => 0x20B, # [@set_storage_state] exec sunxi_sprite_exit(1) => no data 78 | :verify_value => 0x20C, # [@verify_value] compute and return CRC of given mem block => AWFESVerifyStatusResponse 79 | :verify_status => 0x20D, # [@verify_status] read len 12 => AWFESVerifyStatusResponse 80 | :flash_size_probe => 0x20E, # read len 4 => sunxi_sprite_size() 81 | :tool_mode => 0x20F, # [@set_tool_mode] can be used to reboot device 82 | # :toolmode is one of AWUBootWorkMode 83 | # :nextmode is desired mode 84 | :memset => 0x210, # can be used to fill memory with desired value (byte) 85 | :pmu => 0x211, # change voltage setting 86 | :unseqmem_read => 0x212, # unsequenced memory read 87 | :unseqmem_write => 0x213, 88 | # From https://github.com/allwinner-zh/bootloader unavailable on most tablets <2015 year 89 | :fes_reset_cpu => 0x214, 90 | :low_power_manger => 0x215, 91 | :force_erase => 0x220, 92 | :force_erase_key => 0x221, 93 | :query_secure => 0x230 # [@query_secure] 94 | } 95 | 96 | # Mode returned by FELCmd[:verify_device] 97 | AWDeviceMode = { 98 | :null => 0x0, 99 | :fel => 0x1, 100 | :fes => 0x2, # also :srv 101 | :update_cool => 0x3, 102 | :update_hot => 0x4 103 | } 104 | 105 | AWSecureStatusMode = { 106 | :sunxi_normal_mode => 0x0, 107 | :sunxi_secure_mode_with_secureos => 0x1, 108 | :sunxi_secure_mode_no_secureos => 0x2, 109 | :sunxi_secure_mode => 0x3, 110 | :sunxi_secure_mode_unknown => -1 # added by me 111 | } 112 | 113 | # U-boot mode (uboot_spare_head.boot_data.work_mode,0xE0 offset) 114 | AWUBootWorkMode = { 115 | :boot => 0x0, # normal start 116 | :usb_tool_product => 0x4, # if :action => :none, then reboots device 117 | :usb_tool_update => 0x8, 118 | :usb_product => 0x10, # FES mode 119 | :card_product => 0x11, # SD-card flash 120 | :usb_debug => 0x12, # FES mode with debug 121 | :sprite_recovery => 0x13, 122 | :usb_update => 0x20, # USB upgrade (automatically inits nand!) 123 | :erase_key => 0x20, # replaced on A83 124 | :outer_update => 0x21 # external disk upgrade 125 | } 126 | 127 | # Used as argument for FES_SET_TOOL_MODE. Names are self-explaining 128 | AWActions = { 129 | :none => 0x0, 130 | :normal => 0x1, 131 | :reboot => 0x2, 132 | :shutdown => 0x3, 133 | :reupdate => 0x4, 134 | :boot => 0x5, 135 | :sprite_test => 0x6 136 | } 137 | 138 | # Flag for FESCmd[:transmit] 139 | FESTransmiteFlag = { 140 | :write => 0x10, # aka :download 141 | :read => 0x20, # aka :upload 142 | # used on boot1.0 (index must be | :write) 143 | :start => 0x40, 144 | :finish => 0x80, 145 | } 146 | 147 | #TAGS FOR FES_DOWN 148 | AWTags = { 149 | :none => 0x0, 150 | :dram => 0x7F00, 151 | :mbr => 0x7F01, # this flag actually perform erase 152 | :uboot => 0x7F02, 153 | :boot1 => 0x7F02, 154 | :boot0 => 0x7F03, 155 | :erase => 0x7F04, # forces platform->eraseflag 156 | :pmu_set => 0x7F05, 157 | :unseq_mem_for_read => 0x7F06, 158 | :unseq_mem_for_write => 0x7F07, 159 | :full_size => 0x7F10, # as seen on A80, download whole image at once 160 | :flash => 0x8000, # used only for writing 161 | :finish => 0x10000, 162 | :start => 0x20000, 163 | } 164 | 165 | #csw_status of AWUSBResponse 166 | AWUSBStatus = { 167 | :ok => 0, 168 | :fail => 1 169 | } 170 | 171 | #FES STORAGE TYPE 172 | FESIndex = { 173 | :dram => 0x0, 174 | :physical => 0x1, 175 | :nand => 0x2, 176 | :log => 0x2, 177 | # these below are usable on boot 1.0 178 | :card => 0x3, 179 | :spinor => 0x3, 180 | :nand2 => 0x20 # encrypted data write? 181 | } 182 | 183 | # Result of FES_QUERY_STORAGE 184 | AWStorageType = { 185 | :nand => 0x0, 186 | :card => 0x1, 187 | :card2 => 0x2, 188 | :spinor => 0x3, 189 | :unknown => -1 # added by me 190 | } 191 | 192 | #Livesuit image attributes 193 | AWImageAttr = { 194 | :res => 0x800, 195 | :length => 0x80000, 196 | :encode => 0x4000000, 197 | :compress => 0x80000000 198 | } 199 | 200 | #FES run types (AWFELMessage->len) 201 | AWRunContext = { 202 | :none => 0x0, 203 | :has_param => 0x1, 204 | :fet => 0x10, 205 | :gen_code => 0x20, 206 | :fed => 0x30 207 | } 208 | 209 | #Live suit item types 210 | AWItemType = { 211 | :common => "COMMON", 212 | :info => "INFO", 213 | :bootrom => "BOOTROM", 214 | :fes => "FES", 215 | :fet => "FET", 216 | :fed => "FED", 217 | :fex => "FEX", 218 | :boot0 => "BOOT0", 219 | :boot1 => "BOOT1", 220 | :rootfsfat12 => "RFSFAT12", 221 | :rootfsfat16 => "RFSFAT16", 222 | :rootfsfat32 => "FFSFAT32", 223 | :userfsfat12 => "UFSFAT12", 224 | :userfsfat16 => "UFSFAT16", 225 | :userfsfat32 => "UFSFAT32", 226 | :phoenix_script => "PXSCRIPT", 227 | :phoenix_tools => "PXTOOLS", 228 | :audio_dsp => "AUDIODSP", 229 | :video_dsp => "VIDEODSP", 230 | :font => "FONT", 231 | :flash_drv => "FLASHDRV", 232 | :os_core => "OS_CORE", 233 | :driver => "DRIVER", 234 | :pic => "PICTURE", 235 | :audio => "AUDIO", 236 | :video => "VIDEO", 237 | :application => "APP" 238 | } 239 | -------------------------------------------------------------------------------- /FELHelpers.rb: -------------------------------------------------------------------------------- 1 | # FELHelpers.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | raise "Use ./felix to execute program!" if File.basename($0) == File.basename(__FILE__) 18 | 19 | # Fix for bytesize function 20 | class StringIO 21 | def bytesize 22 | size 23 | end 24 | end 25 | 26 | # Change the way library allocs memory to avoid nasty memory segmentation 27 | class LIBUSB::Transfer 28 | # Allocate #FELIX_MAX_CHUNK bytes of data buffer for input transfer. 29 | # 30 | # @param [Fixnum] len Number of bytes to allocate 31 | # @param [String, nil] data some data to initialize the buffer with 32 | def alloc_buffer(len, data=nil) 33 | if !@buffer 34 | # HACK: Avoid crash when memory is reallocated 35 | @buffer = FFI::MemoryPointer.new(FELIX_MAX_CHUNK, 1, false) 36 | end 37 | @buffer.put_bytes(0, data) if data 38 | @transfer[:buffer] = @buffer 39 | @transfer[:length] = len 40 | end 41 | 42 | # Set output data that should be sent. 43 | def buffer=(data) 44 | alloc_buffer(data.bytesize, data) 45 | end 46 | end 47 | 48 | 49 | class String # @visibility private 50 | def camelize # @visibility private 51 | self.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase } 52 | end 53 | end 54 | 55 | class Symbol # @visibility private 56 | def camelize # @visibility private 57 | self.to_s.camelize 58 | end 59 | end 60 | 61 | # Contains support methods 62 | class FELHelpers 63 | class << self 64 | # Convert board id to string 65 | # @param id [Integer] board id 66 | # @return [String] board name 67 | def board_id_to_str(id) 68 | board = case (id >> 8 & 0xFFFF) 69 | when 0x1610 then "Allwinner AXX (sunxi)" 70 | when 0x1623 then "Allwinner A10 (sun4i)" 71 | when 0x1625 then "Allwinner A13/A10s (sun5i)" 72 | when 0x1633 then "Allwinner A31 (sun6i)" 73 | when 0x1639 then "Allwinner A80/A33 (sun9i)" 74 | when 0x1650 then "Allwinner A23 (sun7i)" 75 | when 0x1651 then "Allwinner A20 (sun7i)" 76 | when 0x1667 then "Allwinner A33 (sun8i)" 77 | when 0x1673 then "Allwinner A83 (sun8i)" 78 | else 79 | "Unknown: (0x%x)" % (id >> 8 & 0xFFFF) 80 | end 81 | board << ", revision #{id & 0xFF}" 82 | end 83 | 84 | # Convert sys_config.fex port to integer form 85 | # @param port [String] port e.g. "port:PB22<2><1>" 86 | # @return [Integer] integer form e.g. 0x7C4AC1 87 | # @raise [FELError] if cannot parse port 88 | # @example how to encode port (on example: port:PH20<2><1>): 89 | # ? : 0x40000 = 0x40000 90 | # group: 0x80000 + 'H' - 'A' = 0x80007 (H) 91 | # pin: 0x100000 + (20 << 5) = 0x100280 (20) 92 | # func: 0x200000 + (2 << 10) = 0x200800 (<2>) 93 | # mul: 0x400000 + (1 << 14) = 0x404000 (<1>) 94 | # pull: 0x800000 + (? << 16) = 0 () 95 | # data: 0x1000000 +(? << 18) = 0 () 96 | # sum = 0x7C4A87 97 | # @todo find out how to encode port:powerX<> 98 | def port_to_id(port) 99 | raise FELError, "Failed to parse port string (#{port})" unless port=~ 100 | /port:p\w\d\d?<[\w<>]+>$/i 101 | id = 0x40000 102 | port.match(%r{port:p(?\w)(?\d\d?)(?:<(?\d+)>)? 103 | (?:<(?\d+)>)?(?:<(?\d+)>)?(?:<(?\d+)>)?}ix) do |m| 104 | id+= 0x80000 + m[:group].ord - 'A'.ord 105 | id+= 0x100000 + (m[:pin].to_i << 5) 106 | id+= 0x200000 + (m[:func].to_i << 10) if m[:func] 107 | id+= 0x400000 + (m[:mul].to_i << 14) if m[:mul] 108 | id+= 0x800000 + (m[:pull].to_i << 16) if m[:pull] 109 | id+= 0x1000000 + (m[:data].to_i << 18) if m[:data] 110 | end 111 | id 112 | end 113 | 114 | # Convert tag mask to string 115 | # @param tags [Integer] tag flag 116 | # @return [String] human readable tags delimetered by | 117 | def tags_to_s(tags) 118 | r = "" 119 | AWTags.each do |k, v| 120 | next if tags>0 && k == :none 121 | r << "|" if r.length>0 && tags & v == v 122 | r << "#{k.to_s}" if tags & v == v 123 | end 124 | r 125 | end 126 | 127 | # Decode packet 128 | # @param packet [String] packet data without USB header 129 | # @param dir [Symbol] last connection direction (`:read` or `:write`) 130 | # @return [Symbol] direction of the packet 131 | def debug_packet(packet, dir) 132 | if packet[0..3] == "AWUC" && packet.length == 32 133 | p = AWUSBRequest.read(packet) 134 | print "--> (% 5d) " % packet.length 135 | case p.cmd 136 | when USBCmd[:read] 137 | print "USBRead".yellow 138 | dir = :read 139 | when USBCmd[:write] 140 | print "USBWrite".yellow 141 | dir = :write 142 | else 143 | print "AWUnknown (0x%x)".red % p.cmd 144 | end 145 | puts "\t(Prepare for #{dir} of #{p.len} bytes)" 146 | #puts p.inspect 147 | elsif packet.length == 20 && packet[16..19] == "AWUC" 148 | p = AWUSBRequestV2.read(packet) 149 | print "--> (% 5d) " % packet.length 150 | dir = (p.cmd == FESCmd[:download] ? :write : :read) 151 | print "FES#{FESCmd.key(p.cmd).camelize}". 152 | light_blue if FESCmd.has_value?(p.cmd) 153 | puts "\tTag: #{tags_to_s(p.flags)} (0x%04x), addr (0x%x)" % [p.flags, p.address] 154 | puts "\t(Prepare for #{dir} of #{p.len} bytes)" if p.len > 0 155 | elsif packet[0..7] == "AWUSBFEX" 156 | p = AWFELVerifyDeviceResponse.read(packet) 157 | puts "<-- (% 5d) " % packet.bytesize << "FELVerifyDeviceResponse". 158 | light_blue << "\t%s, FW: %d, mode: %s" % [ board_id_to_str(p.board), p.fw, 159 | AWDeviceMode.key(p.mode) ] 160 | elsif packet[0..3] == "AWUS" && packet.length == 13 161 | p = AWUSBResponse.read(packet) 162 | puts "<-- (% 5d) " % packet.bytesize << "AWUSBResponse".yellow << 163 | "\t0x%x, status %s" % [ p.tag, AWUSBStatus.key(p.csw_status) ] 164 | elsif packet[0..3] == "DRAM" && packet.length == 136 165 | p = AWDRAMData.read(packet) 166 | puts "<-- (% 5d) " % packet.bytesize << "AWDRAMData".light_blue 167 | p p 168 | elsif packet.length == 512 169 | p = AWSystemParameters.read(packet) 170 | puts "<-- (% 5d) " % packet.bytesize << "AWSystemParameters".light_blue 171 | p.pp 172 | elsif packet.length == 12 && dir == :read 173 | p = AWFESVerifyStatusResponse.read(packet) 174 | puts "<-- (% 5d) " % packet.bytesize << "AWFESVerifyStatusResponse ". 175 | light_blue << "flags: 0x%x, crc: 0x%x, last_err/crc %d" % [p.flags, 176 | p.fes_crc, p.crc] 177 | else 178 | return :unk if dir == :unk 179 | print (dir == :write ? "--> " : "<-- ") << "(% 5d) " % packet.bytesize 180 | if packet.length == 16 181 | p = AWFELMessage.read(packet) 182 | case p.cmd 183 | when FELCmd[:verify_device] then puts "FELVerifyDevice" 184 | .light_blue << " (0x#{FELCmd[:verify_device]})" 185 | when FESCmd[:transmit] 186 | p = AWFESTrasportRequest.read(packet) 187 | print "FES#{FESCmd.key(p.cmd).camelize}: ".light_blue 188 | print FESTransmiteFlag.key(p.flags).to_s 189 | print "(0x%04x)" % p.flags unless FESTransmiteFlag.key(p.flags) 190 | puts ", tag #{p.tag}, index #{p.media_index}, addr 0x%08x, len %d," % [ 191 | p.address, p.len] << " reserved %s" % p.reserved.inspect 192 | when FESCmd[:download], FESCmd[:verify_status], FELCmd[:download], 193 | FELCmd[:upload], FESCmd[:run], FELCmd[:run] 194 | p = AWFELMessage.read(packet) 195 | print "FEL#{FELCmd.key(p.cmd).camelize}:". 196 | light_blue if FELCmd.has_value?(p.cmd) 197 | print "FES#{FESCmd.key(p.cmd).camelize}:". 198 | light_blue if FESCmd.has_value?(p.cmd) 199 | puts " tag: #{p.tag}, %d bytes @ 0x%02x" % [p.len, p.address] << 200 | ", flags #{tags_to_s(p.flags)} (0x%04x)" % p.flags 201 | else 202 | print "FEL#{FELCmd.key(p.cmd).camelize}". 203 | light_blue if FELCmd.has_value?(p.cmd) 204 | print "FES#{FESCmd.key(p.cmd).camelize}". 205 | light_blue if FESCmd.has_value?(p.cmd) 206 | if FESCmd.has_value?(p.cmd) || FELCmd.has_value?(p.cmd) 207 | puts " (0x%.2X): " % p.cmd << "#{packet.to_hex_string[0..46]}" 208 | else 209 | print "\n" 210 | $options[:verbose] ? Hexdump.dump(packet) : Hexdump.dump(packet[0..63]) 211 | end 212 | end 213 | elsif packet.length == 8 214 | p = AWFELStatusResponse.read(packet) 215 | puts "FELStatusResponse\t".yellow << 216 | "mark #{p.mark}, tag #{p.tag}, state #{p.state}" 217 | else 218 | print "\n" 219 | $options[:verbose] ? Hexdump.dump(packet) : Hexdump.dump(packet[0..63]) 220 | end 221 | end 222 | dir 223 | end 224 | 225 | # Decode USBPcap packets exported from Wireshark in C header format 226 | # e.g. { 227 | # 0x1c, 0x00, 0x10, 0x60, 0xa9, 0x95, 0x00, 0xe0, /* ...`.... */ 228 | # 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, /* ........ */ 229 | # 0x01, 0x01, 0x00, 0x0d, 0x00, 0x80, 0x02, 0x00, /* ........ */ 230 | # 0x00, 0x00, 0x00, 0x02 /* .... */ 231 | # }; 232 | # @param file [String] file name 233 | def debug_packets(file) 234 | return if file.nil? 235 | print "* Processing..." 236 | packets = Array.new 237 | contents = File.read(file) 238 | header_size = nil 239 | i = 0 240 | contents.scan(/^.*?{(.*?)};/m) do |packet| 241 | i+=1 242 | hstr = "" 243 | packet[0].scan(/0x([0-9A-Fa-f]{2})/m) { |hex| hstr << hex[0] } 244 | print "\r* Processing..." << "#{i}".yellow << " found" 245 | #Strip USB header 246 | begin 247 | #try to guess header size 248 | header_size = hstr.to_byte_string.index('AWUC') unless header_size 249 | header_size = hstr.to_byte_string.index('AWUS') unless header_size 250 | next unless header_size 251 | packets << hstr.to_byte_string[header_size..-1] if hstr.to_byte_string[header_size..-1] != nil 252 | rescue RuntimeError => e 253 | puts "Error : (#{e.message}) at #{e.backtrace[0]}" 254 | puts "Failed to decode packet: (#{hstr.length / 2}), #{hstr}" 255 | end 256 | end 257 | puts 258 | 259 | dir = :unk 260 | packets.each do |packet| 261 | next if packet.length < 4 262 | dir = debug_packet(packet, dir) 263 | end 264 | end 265 | 266 | # Decrypt Livesuit image header using RC6 algorithm 267 | # @param data [String] encrypted binary data 268 | # @param key [Symbol] encryption key (:header, :item, :data) 269 | # @return decryped binary data 270 | def decrypt(data, key) 271 | # Fill block if last chunk is < 16 bytes 272 | align = 16 - data.bytesize % 16 273 | if align 274 | data << "\0" * align 275 | end 276 | out = RC6Keys[key].decrypt(data) 277 | out.byteslice(0...-align) 278 | end 279 | 280 | # Load Livesuit image header, and display info 281 | # @param file [String] filename 282 | def show_image_info(file) 283 | encrypted = false 284 | img = File.read(file, 1024) # Read header 285 | if img.byteslice(0, 8) != FELIX_IMG_HEADER 286 | puts "Warning:".red << " Image is encrypted!".yellow 287 | encrypted = true 288 | end 289 | print "Decrypting..." if encrypted 290 | img = decrypt(img, :header) if encrypted 291 | raise FELError, "Unrecognized image format" if img.byteslice(0, 8) != 292 | FELIX_IMG_HEADER 293 | # @todo read header version 294 | item_count = img[encrypted ? 0x38 : 0x3C, 4].unpack("V").first 295 | raise "Firmware contains no items!" if item_count == 0 296 | 297 | for i in 1..item_count 298 | print "\rDecrypting...: #{i}/#{item_count}".green if encrypted 299 | item = File.read(file, 1024, (i * 1024)) # Read item 300 | item = decrypt(item, :item) if encrypted 301 | img << item 302 | end 303 | puts if encrypted 304 | puts AWImage.read(img).inspect 305 | end 306 | 307 | # Generate AW-style checksum for given data block 308 | # @param data [String] memory block to compute 309 | # @note Original C function is add_sum from u-boot-2011.09/sprite/sprite_verify.c 310 | def checksum(data) 311 | sum = 0 312 | ints = data.unpack("V*") 313 | ints.each do |i| 314 | sum += i 315 | end 316 | 317 | case(data.length & 3) 318 | when 0 319 | sum 320 | when 1 321 | sum += ints[-1] & 0x000000ff 322 | when 2 323 | sum += ints[-1] & 0x0000ffff 324 | when 3 325 | sum += ints[-1] & 0x00ffffff 326 | end 327 | 328 | # Trim 64 -> 32 bit value 329 | sum & 0xffffffff 330 | end 331 | 332 | # Create DRAM config based on sys_config.fex, sys_config1.fex 333 | # @param file [String] sys_config.fex file 334 | # @param legacy [TrueClass,FalseClass] what strcture to create 335 | # @return [AWSystemLegacyParameters,AWSystemParameters] dram config 336 | def create_dram_config(file = nil, legacy = false) 337 | dram_cfg = nil 338 | cfg = file 339 | cfg.tr!("\0","") 340 | cfg_ini = IniFile.new( :content => cfg, :encoding => "UTF-8") 341 | if legacy 342 | dram_cfg = AWLegacySystemParameters.new 343 | # Assign values, but left defaults if entry doesn't exist 344 | dram_cfg.chip = cfg_ini[:platform]["chip"] if cfg_ini[:platform]["chip"] 345 | dram_cfg.pid = cfg_ini[:platform]["pid"] if cfg_ini[:platform]["pid"] 346 | dram_cfg.sid = cfg_ini[:platform]["sid"] if cfg_ini[:platform]["sid"] 347 | dram_cfg.bid = cfg_ini[:platform]["bid"] if cfg_ini[:platform]["bid"] 348 | dram_cfg.uart_debug_tx = port_to_id(cfg_ini[:uart_para][ 349 | "uart_debug_tx"]) if cfg_ini[:uart_para]["uart_debug_tx"] 350 | dram_cfg.uart_debug_port = cfg_ini[:uart_para]["uart_debug_port"] if 351 | cfg_ini[:uart_para]["uart_debug_port"] 352 | dram_cfg.dram_baseaddr = cfg_ini[:dram_para]["dram_baseaddr"] if 353 | cfg_ini[:dram_para]["dram_baseaddr"] 354 | dram_cfg.dram_clk = cfg_ini[:dram_para]["dram_clk"] if 355 | cfg_ini[:dram_para]["dram_clk"] 356 | dram_cfg.dram_type = cfg_ini[:dram_para]["dram_type"] if 357 | cfg_ini[:dram_para]["dram_type"] 358 | dram_cfg.dram_rank_num = cfg_ini[:dram_para]["dram_rank_num"] if 359 | cfg_ini[:dram_para]["dram_rank_num"] 360 | dram_cfg.dram_chip_density = cfg_ini[:dram_para]["dram_chip_density"] if 361 | cfg_ini[:dram_para]["dram_chip_density"] 362 | dram_cfg.dram_io_width = cfg_ini[:dram_para]["dram_io_width"] if 363 | cfg_ini[:dram_para]["dram_io_width"] 364 | dram_cfg.dram_bus_width = cfg_ini[:dram_para]["dram_bus_width"] if 365 | cfg_ini[:dram_para]["dram_bus_width"] 366 | dram_cfg.dram_cas = cfg_ini[:dram_para]["dram_cas"] if 367 | cfg_ini[:dram_para]["dram_cas"] 368 | dram_cfg.dram_zq = cfg_ini[:dram_para]["dram_zq"] if 369 | cfg_ini[:dram_para]["dram_zq"] 370 | dram_cfg.dram_odt_en = cfg_ini[:dram_para]["dram_odt_en"] if 371 | cfg_ini[:dram_para]["dram_odt_en"] 372 | dram_cfg.dram_size = cfg_ini[:dram_para]["dram_size"] if 373 | cfg_ini[:dram_para]["dram_size"] 374 | dram_cfg.dram_tpr0 = cfg_ini[:dram_para]["dram_tpr0"] if 375 | cfg_ini[:dram_para]["dram_tpr0"] 376 | dram_cfg.dram_tpr1 = cfg_ini[:dram_para]["dram_tpr1"] if 377 | cfg_ini[:dram_para]["dram_tpr1"] 378 | dram_cfg.dram_tpr2 = cfg_ini[:dram_para]["dram_tpr2"] if 379 | cfg_ini[:dram_para]["dram_tpr2"] 380 | dram_cfg.dram_tpr3 = cfg_ini[:dram_para]["dram_tpr3"] if 381 | cfg_ini[:dram_para]["dram_tpr3"] 382 | dram_cfg.dram_tpr4 = cfg_ini[:dram_para]["dram_tpr4"] if 383 | cfg_ini[:dram_para]["dram_tpr4"] 384 | dram_cfg.dram_tpr5 = cfg_ini[:dram_para]["dram_tpr5"] if 385 | cfg_ini[:dram_para]["dram_tpr5"] 386 | dram_cfg.dram_emr1 = cfg_ini[:dram_para]["dram_emr1"] if 387 | cfg_ini[:dram_para]["dram_emr1"] 388 | dram_cfg.dram_emr2 = cfg_ini[:dram_para]["dram_emr2"] if 389 | cfg_ini[:dram_para]["dram_emr2"] 390 | dram_cfg.dram_emr3 = cfg_ini[:dram_para]["dram_emr3"] if 391 | cfg_ini[:dram_para]["dram_emr3"] 392 | else 393 | dram_cfg = AWSystemParameters.new 394 | dram_cfg.uart_debug_tx = port_to_id(cfg_ini[:uart_para][ 395 | "uart_debug_tx"]) if cfg_ini[:uart_para]["uart_debug_tx"] 396 | dram_cfg.uart_debug_port = cfg_ini[:uart_para]["uart_debug_port"] if 397 | cfg_ini[:uart_para]["uart_debug_port"] 398 | dram_cfg.dram_clk = cfg_ini[:dram_para]["dram_clk"] if 399 | cfg_ini[:dram_para]["dram_clk"] 400 | dram_cfg.dram_type = cfg_ini[:dram_para]["dram_type"] if 401 | cfg_ini[:dram_para]["dram_type"] 402 | dram_cfg.dram_zq = cfg_ini[:dram_para]["dram_zq"] if 403 | cfg_ini[:dram_para]["dram_zq"] 404 | dram_cfg.dram_odt_en = cfg_ini[:dram_para]["dram_odt_en"] if 405 | cfg_ini[:dram_para]["dram_odt_en"] 406 | dram_cfg.dram_para1 = cfg_ini[:dram_para]["dram_para1"] if 407 | cfg_ini[:dram_para]["dram_para1"] 408 | dram_cfg.dram_para2 = cfg_ini[:dram_para]["dram_para2"] if 409 | cfg_ini[:dram_para]["dram_para2"] 410 | dram_cfg.dram_mr0 = cfg_ini[:dram_para]["dram_mr0"] if 411 | cfg_ini[:dram_para]["dram_mr0"] 412 | dram_cfg.dram_mr1 = cfg_ini[:dram_para]["dram_mr1"] if 413 | cfg_ini[:dram_para]["dram_mr1"] 414 | dram_cfg.dram_mr2 = cfg_ini[:dram_para]["dram_mr2"] if 415 | cfg_ini[:dram_para]["dram_mr2"] 416 | dram_cfg.dram_mr3 = cfg_ini[:dram_para]["dram_mr3"] if 417 | cfg_ini[:dram_para]["dram_mr3"] 418 | dram_cfg.dram_tpr0 = cfg_ini[:dram_para]["dram_tpr0"] if 419 | cfg_ini[:dram_para]["dram_tpr0"] 420 | dram_cfg.dram_tpr1 = cfg_ini[:dram_para]["dram_tpr1"] if 421 | cfg_ini[:dram_para]["dram_tpr1"] 422 | dram_cfg.dram_tpr2 = cfg_ini[:dram_para]["dram_tpr2"] if 423 | cfg_ini[:dram_para]["dram_tpr2"] 424 | dram_cfg.dram_tpr3 = cfg_ini[:dram_para]["dram_tpr3"] if 425 | cfg_ini[:dram_para]["dram_tpr3"] 426 | dram_cfg.dram_tpr4 = cfg_ini[:dram_para]["dram_tpr4"] if 427 | cfg_ini[:dram_para]["dram_tpr4"] 428 | dram_cfg.dram_tpr5 = cfg_ini[:dram_para]["dram_tpr5"] if 429 | cfg_ini[:dram_para]["dram_tpr5"] 430 | dram_cfg.dram_tpr6 = cfg_ini[:dram_para]["dram_tpr6"] if 431 | cfg_ini[:dram_para]["dram_tpr6"] 432 | dram_cfg.dram_tpr7 = cfg_ini[:dram_para]["dram_tpr7"] if 433 | cfg_ini[:dram_para]["dram_tpr7"] 434 | dram_cfg.dram_tpr8 = cfg_ini[:dram_para]["dram_tpr8"] if 435 | cfg_ini[:dram_para]["dram_tpr8"] 436 | dram_cfg.dram_tpr9 = cfg_ini[:dram_para]["dram_tpr9"] if 437 | cfg_ini[:dram_para]["dram_tpr9"] 438 | dram_cfg.dram_tpr10 = cfg_ini[:dram_para]["dram_tpr10"] if 439 | cfg_ini[:dram_para]["dram_tpr10"] 440 | dram_cfg.dram_tpr11 = cfg_ini[:dram_para]["dram_tpr11"] if 441 | cfg_ini[:dram_para]["dram_tpr11"] 442 | dram_cfg.dram_tpr12 = cfg_ini[:dram_para]["dram_tpr12"] if 443 | cfg_ini[:dram_para]["dram_tpr12"] 444 | dram_cfg.dram_tpr13 = cfg_ini[:dram_para]["dram_tpr13"] if 445 | cfg_ini[:dram_para]["dram_tpr13"] 446 | dram_cfg.dram_size = cfg_ini[:dram_para]["dram_size"] if 447 | cfg_ini[:dram_para]["dram_size"] 448 | end 449 | dram_cfg 450 | end 451 | 452 | end 453 | end 454 | -------------------------------------------------------------------------------- /FELStructs.rb: -------------------------------------------------------------------------------- 1 | # FELStructs.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | raise "Use ./felix to execute program!" if File.basename($0) == File.basename(__FILE__) 18 | 19 | # Program exception filter class 20 | class FELError < StandardError 21 | end 22 | 23 | # Fatal exception filter class 24 | class FELFatal < StandardError 25 | end 26 | 27 | # @visibility private 28 | class BinData::Record 29 | # Print nicely formatted structure 30 | def pp 31 | self.each_pair do |k ,v| 32 | print " #{k}".yellow.ljust(40) 33 | if v.instance_of?(BinData::String) || v.instance_of?(BinData::Array) 34 | puts v.inspect 35 | else 36 | puts "0x%08x" % v 37 | end 38 | end 39 | end 40 | end 41 | 42 | class AWUSBRequest < BinData::Record # size 32 43 | string :magic, :read_length => 4, :initial_value => "AWUC" 44 | uint32le :tag, :initial_value => 0 45 | uint32le :len, :initial_value => 16 46 | uint16le :reserved1, :initial_value => 0 47 | uint8 :reserved2, :initial_value => 0 48 | uint8 :cmd_len, :value => 0xC 49 | uint8 :cmd, :initial_value => USBCmd[:write] 50 | uint8 :reserved3, :initial_value => 0 51 | uint32le :len2, :value => :len 52 | array :reserved, :type => :uint8, :initial_length => 10, :value => 0 53 | end 54 | 55 | #0000 06 02 56 | # 00 00 00 00 57 | # 00 80 00 00 => data_len 58 | # 03 7f 59 | # 01 00 60 | #0010 41 57 55 43 AWUC 61 | class AWUSBRequestV2 < BinData::Record # size 20, used on A83T 62 | uint32le :cmd, :initial_value => FELCmd[:verify_device] 63 | uint32le :address, :initial_value => 0 64 | uint32le :len, :initial_value => 0 65 | uint32le :flags, :initial_value => AWTags[:none] # one or more of FEX_TAGS 66 | string :magic, :read_length => 4, :initial_value => "AWUC" 67 | end 68 | 69 | class AWUSBResponse < BinData::Record # size 13 70 | string :magic, :read_length => 4, :initial_value => "AWUS" 71 | uint32le :tag 72 | uint32le :residue 73 | uint8 :csw_status # != 0, then fail 74 | end 75 | 76 | class AWFELStandardRequest < BinData::Record # size 16 77 | uint16le :cmd, :initial_value => FELCmd[:verify_device] 78 | uint16le :tag, :initial_value => 0 79 | array :reserved, :type => :uint8, :initial_length => 12, :value => 0 80 | end 81 | 82 | # Extended struct for FEL/FES commands 83 | # Structure size: 16 84 | class AWFELMessage < BinData::Record 85 | uint16le :cmd, :initial_value => FELCmd[:download] 86 | uint16le :tag, :initial_value => 0 87 | uint32le :address # addr + totalTransLen / 512 => FES_MEDIA_INDEX_PHYSICAL, 88 | # FES_MEDIA_INDEX_LOG (NAND) 89 | # addr + totalTransLen => FES_MEDIA_INDEX_DRAM 90 | # totalTransLen => 65536 (max chunk) 91 | uint32le :len # also next_mode for :tool_mode 92 | uint32le :flags, :initial_value => AWTags[:none] # one or more of FEX_TAGS 93 | end 94 | 95 | # Boot 1.0 way to download data 96 | class AWFESTrasportRequest < BinData::Record # size 16 97 | uint16le :cmd, :value => FESCmd[:transmit] 98 | uint16le :tag, :initial_value => 0 99 | uint32le :address 100 | uint32le :len 101 | uint8 :media_index, :initial_value => FESIndex[:dram] 102 | uint8 :flags, :initial_value => FESTransmiteFlag[:write] 103 | array :reserved, :type => :uint8, :initial_length => 2, :value => 0 104 | end 105 | 106 | class AWFELStatusResponse < BinData::Record # size 8 107 | uint16le :mark, :asserted_value => 0xFFFF 108 | uint16le :tag 109 | uint8 :state 110 | array :reserved, :type => :uint8, :initial_length => 3 111 | end 112 | 113 | class AWFELVerifyDeviceResponse < BinData::Record # size 32 114 | string :magic, :read_length => 8, :initial_value => "AWUSBFEX" 115 | uint32le :board 116 | uint32le :fw 117 | uint16le :mode 118 | uint8 :data_flag 119 | uint8 :data_length 120 | uint32le :data_start_address 121 | array :reserved, :type => :uint8, :initial_length => 8 122 | 123 | def inspect 124 | out = String.new 125 | self.each_pair do |k, v| 126 | out << " #{k}".ljust(25).yellow 127 | case k 128 | when :board then out << FELHelpers.board_id_to_str(v) << "\n" 129 | when :mode then out << AWDeviceMode.key(v).to_s << "\n" 130 | when :data_flag, :data_length, :data_start_address 131 | out << "0x%08x" % v << "\n" 132 | else 133 | out << "#{v}" << "\n" 134 | end 135 | end 136 | out 137 | end 138 | 139 | end 140 | 141 | class AWFESVerifyStatusResponse < BinData::Record # size 12 142 | uint32le :flags # always 0x6a617603 143 | uint32le :fes_crc 144 | uint32le :crc # also last_error (0 if OK, -1 if fail) 145 | end 146 | 147 | # Used by FES[:run] with has_param flag 148 | class AWFESRunArgs < BinData::Record # size 16 149 | array :args, :type => :uint32le, :initial_length => 4 150 | end 151 | 152 | # Used by FES[:info] 153 | class AWFESInfoResponse < BinData::Record # size 32 154 | array :response, :type => :uint32le, :initial_length => 8 155 | end 156 | 157 | class AWDRAMData < BinData::Record # size 136? 158 | string :magic, :read_length => 4, :initial_value => "DRAM" 159 | uint32le :unk 160 | uint32le :dram_clk 161 | uint32le :dram_type 162 | uint32le :dram_zq 163 | uint32le :dram_odt_en 164 | uint32le :dram_para1 165 | uint32le :dram_para2 166 | uint32le :dram_mr0 167 | uint32le :dram_mr1 168 | uint32le :dram_mr2 169 | uint32le :dram_mr3 170 | uint32le :dram_tpr0 171 | uint32le :dram_tpr1 172 | uint32le :dram_tpr2 173 | uint32le :dram_tpr3 174 | uint32le :dram_tpr4 175 | uint32le :dram_tpr5 176 | uint32le :dram_tpr6 177 | uint32le :dram_tpr7 178 | uint32le :dram_tpr8 179 | uint32le :dram_tpr9 180 | uint32le :dram_tpr10 181 | uint32le :dram_tpr11 182 | uint32le :dram_tpr12 183 | uint32le :dram_tpr13 184 | array :dram_unknown, :type => :uint32le, :read_until => :eof 185 | end 186 | 187 | # Init data for boot 1.0 188 | # It's created using sys_config.fex, and its product of fes1-2.fex 189 | # Names in brackets are [section] from sys_config.fex, and variable name is a key 190 | # Size 512 191 | # Dump of the struct (A31) 192 | # unsigned char rawData[512] = { 193 | # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 194 | # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 195 | # 0x87, 0x4A, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, [0x38, 0x01, 0x00, 0x00], => dram_clk 196 | # 0x03, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 197 | # 0x00, 0x08, 0xF4, 0x10, 0x11, 0x12, 0x00, 0x00, 0x50, 0x1A, 0x00, 0x00, 198 | # 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 199 | # 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x80, 0x40, 0x01, 0xA7, 0x39, 200 | # 0x4C, 0xE7, 0x92, 0xA0, 0x09, 0xC2, 0x48, 0x29, 0x2C, 0x42, 0x44, 0x89, 201 | # 0x80, 0x84, 0x02, 0x30, 0x97, 0x32, 0x2A, 0x00, 0xA8, 0x4F, 0x03, 0x05, 202 | # 0xD8, 0x53, 0x63, 0x03, (0x00, 0x00, 0x00, 0x00)* 203 | # }; 204 | # @note default values are for A31s (sun8iw1p2) 205 | class AWSystemParameters < BinData::Record 206 | uint32le :chip, :initial_value => 0 # 0x00 [platform] 207 | uint32le :pid, :initial_value => 0 # 0x04 [platform] 208 | uint32le :sid, :initial_value => 0 # 0x08 [platform] 209 | uint32le :bid, :initial_value => 0 # 0x0C [platform] 210 | uint32le :unk5 # 0x10 211 | uint32le :unk6 # 0x14 212 | uint32le :uart_debug_tx, :initial_value => 0x7C4A87 # 0x18 [uart_para] 213 | uint32le :uart_debug_port, :inital_value => 0 # 0x1C [uart_para] 214 | uint32le :dram_clk, :initial_value => 240 # 0x20 215 | uint32le :dram_type, :initial_value => 3 # 0x24 216 | uint32le :dram_zq, :initial_value => 0xBB # 0x28 217 | uint32le :dram_odt_en, :initial_value => 0 # 0x2C 218 | uint32le :dram_para1, :initial_value => 0x10F40400 # 0x30, &=0xffff => DRAM size (1024) 219 | uint32le :dram_para2, :initial_value => 0x1211 # 0x34 220 | uint32le :dram_mr0, :initial_value => 0x1A50 # 0x38 221 | uint32le :dram_mr1, :initial_value => 0 # 0x3C 222 | uint32le :dram_mr2, :initial_value => 24 # 0x40 223 | uint32le :dram_mr3, :initial_value => 0 # 0x44 224 | uint32le :dram_tpr0, :initial_value => 0 # 0x48 225 | uint32le :dram_tpr1, :initial_value => 0x80000800 # 0x4C 226 | uint32le :dram_tpr2, :initial_value => 0x46270140 # 0x50 227 | uint32le :dram_tpr3, :initial_value => 0xA0C4284C # 0x54 228 | uint32le :dram_tpr4, :initial_value => 0x39C8C209 # 0x58 229 | uint32le :dram_tpr5, :initial_value => 0x694552AD # 0x5C 230 | uint32le :dram_tpr6, :initial_value => 0x3002C4A0 # 0x60 231 | uint32le :dram_tpr7, :initial_value => 0x2AAF9B # 0x64 232 | uint32le :dram_tpr8, :initial_value => 0x604111D # 0x68 233 | uint32le :dram_tpr9, :initial_value => 0x42DA072 # 0x6C 234 | uint32le :dram_tpr10, :initial_value => 0 # 0x70 235 | uint32le :dram_tpr11, :initial_value => 0 # 0x74 236 | uint32le :dram_tpr12, :initial_value => 0 # 0x78 237 | uint32le :dram_tpr13, :initial_value => 0 # 0x7C 238 | uint32le :dram_size, :initial_value => (1024 << 20) # 0x80, 1024 MB 239 | array :reserved, :type => :uint32le, :initial_length => 95 # 0x84 240 | end 241 | 242 | # size 180 243 | # Used on old CPUs which contains sys_config1.fex & sys_config.fex 244 | class AWLegacySystemParameters < BinData::Record 245 | uint32le :chip, :initial_value => 0x2000000 # 0x00 [platform] 246 | uint32le :pid, :initial_value => 0x2000000 # 0x04 [platform] 247 | uint32le :sid, :initial_value => 0x2000100 # 0x08 [platform] 248 | uint32le :bid, :initial_value => 128 # 0x0C [platform] 249 | uint32le :unk5 # 0x10 250 | uint32le :unk6 # 0x14 251 | uint32le :uart_debug_tx, :initial_value => 0x7C4AC1 # 0x18 [uart_para] 252 | uint32le :uart_debug_port, :inital_value => 0 # 0x1C [uart_para] 253 | array :unk7, :type => :uint32le, :initial_length => 15 # 0x20 254 | uint32le :dram_baseaddr, :initial_value => 0x40000000 # 0x5C 255 | uint32le :dram_clk, :initial_value => 408 # 0x60 256 | uint32le :dram_type, :initial_value => 3 # 0x64 257 | uint32le :dram_rank_num, :initial_value => 1 # 0x68 258 | uint32le :dram_chip_density, :initial_value => 4096 # 0x6C 259 | uint32le :dram_io_width, :initial_value => 16 # 0x70 260 | uint32le :dram_bus_width, :initial_value => 32 # 0x74 261 | uint32le :dram_cas, :initial_value => 6 # 0x78 262 | uint32le :dram_zq, :initial_value => 0x7F # 0x7C 263 | uint32le :dram_odt_en # 0x80 264 | uint32le :dram_size, :initial_value => 1024 # 0x84 265 | uint32le :dram_tpr0, :initial_value => 0x30926692 # 0x88 266 | uint32le :dram_tpr1, :initial_value => 0x1090 # 0x8C 267 | uint32le :dram_tpr2, :initial_value => 0x1A0C8 # 0x90 268 | uint32le :dram_tpr3 # 0x94 269 | uint32le :dram_tpr4 # 0x98 270 | uint32le :dram_tpr5 # 0x9C 271 | uint32le :dram_emr1, :initial_value => 4 # 0xA0 272 | uint32le :dram_emr2 # 0xA4 273 | uint32le :dram_emr3 # 0xA8 274 | array :unk8, :type => :uint32le, :initial_length => 2 # 0xAC 275 | end 276 | 277 | #size 104 278 | class AWSysParaPart < BinData::Record 279 | endian :little 280 | uint32 :address_high, :initial_value => 0 281 | uint32 :address_low 282 | string :classname, :read_length => 32, :trim_padding => true, :initial_value => "DISK" 283 | string :name, :read_length => 32, :trim_padding => true 284 | uint32 :user_type 285 | uint32 :ro 286 | array :reserved, :type => :uint8, :initial_length => 24 287 | end 288 | 289 | # size 97 bytes 290 | class AWSysParaItem < BinData::Record 291 | endian :little 292 | string :name, :read_length => 32, :trim_padding => true 293 | string :filename, :read_length => 32, :trim_padding => true 294 | string :verify_filename, :read_length => 32, :trim_padding => true # checksum of the item 295 | uint8 :encrypt, :initial_value => lambda { name.empty? ? 1 : 0 } # 1 if item is unused 296 | end 297 | 298 | # size 5496, send in FES mode (boot1.0 only) as param to FED 299 | class AWSysPara < BinData::Record 300 | string :magic, :read_length => 8, :initial_value => "SYS_PARA" # 0x00 301 | uint32le :unk1, :initial_value => 256 # 0x08 302 | uint32le :eraseflag, :initial_value => 1 # 0x0C 303 | uint32le :jtag, :initial_value => 1 # 0x10 [not sure] 304 | aw_legacy_system_parameters :dram # 0x14 305 | uint32le :unk4 # 0xC8 306 | uint32le :unk5, :initial_value => 8 # 0xCC 307 | array :unk6, :type => :uint32le, :initial_length => 256 # 0xD0 on some device its 512 length 308 | # and dram aren't of legacy type check that 309 | uint32le :mbr_size, :initial_value => 16384 # 0x4D0 310 | uint32le :part_num # 0x4D4 311 | array :part_items, :type => :aw_sys_para_part, 312 | :initial_length => lambda { part_num } # 0x4D8 313 | array :reserved, :type => :uint32le, 314 | :initial_length => lambda { (14 - part_num) * 26 } # (26 -> sizeof(aw_sys_para_part) /4) 315 | # on other device there are place for 41 316 | uint32le :dl_num # 0xA88 317 | array :dl_items, :type => :aw_sys_para_item, 318 | :initial_length => 14 # 0xA8C (30 on other device) 319 | array :unk8, :type => :uint8le, :initial_length => 1438 320 | end 321 | 322 | # Size 128 323 | class AWSunxiPartition < BinData::Record 324 | endian :little 325 | uint32 :address_high, :initial_value => 0 326 | uint32 :address_low 327 | uint32 :lenhi, :initial_value => 0 328 | uint32 :lenlo 329 | string :classname, :length => 16, :trim_padding => true, :initial_value => "DISK" 330 | string :name, :length => 16, :trim_padding => true 331 | uint32 :user_type, :initial_value => 0x8000 332 | uint32 :keydata, :initial_value => 0 333 | uint32 :ro, :initial_value => 0 334 | # For A83 335 | uint32 :sig_verify 336 | uint32 :sig_erase 337 | array :sig_value, :type => :uint32, :initial_length => 4 338 | uint32 :sig_pubkey; 339 | uint32 :sig_pbumode; 340 | array :reserved, :type => :uint8, :initial_length => 36 341 | end 342 | 343 | # Size 64 344 | class AWSunxiLegacyPartition < BinData::Record 345 | endian :little 346 | uint32 :address_high, :initial_value => 0 347 | uint32 :address_low 348 | uint32 :lenhi, :initial_value => 0 349 | uint32 :lenlo 350 | string :classname, :read_length => 12, :trim_padding => true, :initial_value => "DISK" 351 | string :name, :read_length => 12, :trim_padding => true 352 | uint32 :user_type, :initial_value => 0x8000 353 | uint32 :ro, :initial_value => 0 354 | array :reserved, :type => :uint8, :initial_length => 16 355 | end 356 | 357 | #Newer mbr (softw411), record size: 16384 358 | class AWSunxiMBR < BinData::Record 359 | uint32le :copy, :initial_value => 4 360 | uint32le :mbr_index 361 | uint32le :part_count, :value => lambda { part.select { |p| not p.name.empty? }.count } 362 | uint32le :stamp, :initial_value => 0 363 | array :part, :type => :aw_sunxi_partition, :initial_length => 120 364 | # For A83 365 | uint32le :lockflag 366 | array :reserved, :type => :uint8, :initial_length => (992 - 4) 367 | end 368 | 369 | #Legacy mbr (softw311), record size: 1024 370 | class AWSunxiLegacyMBR < BinData::Record 371 | uint8 :copy, :initial_value => 4 372 | uint8 :mbr_index 373 | uint16le :part_count, :value => lambda { part.select { |p| not p.name.empty? }.count } 374 | array :part, :type => :aw_sunxi_legacy_partition, :initial_length => 15 375 | array :reserved, :type => :uint8, :initial_length => 44 376 | end 377 | 378 | # Unified SUNXI mbr 379 | class AWMBR < BinData::Record 380 | uint32le :crc, :value => lambda { Crc32.calculate(version.to_binary_s << 381 | magic << mbr.to_binary_s, 12+mbr.num_bytes,0) } 382 | uint32le :version, :initial_value => 0x200 383 | string :magic, :read_length => 8, :initial_value => "softw411", 384 | :assert => lambda { ["softw311", "softw411"].include? magic } 385 | choice :mbr, :selection => lambda { magic.to_s } do 386 | aw_sunxi_mbr "softw411" 387 | aw_sunxi_legacy_mbr "softw311" 388 | end 389 | 390 | # Decode sunxi_mbr.fex 391 | # 392 | # Produces following output 393 | # -------------------------- 394 | # * bootloader (nanda) @ 0x8000 [16MB] [0x00000000] 395 | # * env (nandb) @ 0x10000 [16MB] [0x00000000] 396 | # * ... 397 | def inspect 398 | self.each_pair do |k, v| 399 | print "%-40s" % k.to_s.yellow unless k == :mbr 400 | case k 401 | when :crc, :version then puts "0x%08x" % v 402 | when :mbr 403 | v.each_pair do |i, j| 404 | next if i == :reserved 405 | print "%-40s" % i.to_s.yellow unless i == :part 406 | case i 407 | when :part 408 | puts "Partitions:".light_blue 409 | c = 'a' 410 | j.each do |p| 411 | break if p.name.empty? 412 | print "%-40s" % p.name.yellow 413 | puts "(nand%s) @ 0x%08x [% 5d MB] [0x%08x]" % [c, 414 | p.address_low, p.lenlo/2048, p.keydata] 415 | c.next! 416 | end 417 | else 418 | puts "#{j}" 419 | end 420 | end 421 | else 422 | puts "#{v}" 423 | end 424 | end 425 | end 426 | 427 | # Find a partition data by name 428 | # @param name [String] partition name (e.g. system, boot, data) 429 | # @return [AWSunxiLegacyPartition, AWSunxiPartition] a partition if found 430 | def part_by_name(name) 431 | mbr.part.select { |i| i.name == name }.first 432 | end 433 | 434 | end 435 | 436 | # Item structure nested in AWDownloadInfo (72 bytes) 437 | class AWDownloadItem < BinData::Record 438 | endian :little 439 | string :name, :read_length => 16, :trim_padding => true 440 | uint32 :address_high, :initial_value => 0 441 | uint32 :address_low 442 | uint32 :lenhi, :initial_value => 0 443 | uint32 :lenlo 444 | string :filename, :read_length => 16, :trim_padding => true 445 | string :verify_filename, :read_length => 16, :trim_padding => true # checksum of the item 446 | uint32 :encrypt, :initial_value => 0 447 | uint32 :verify, :initial_value => 0 448 | end 449 | 450 | # Legacy item structure nested in AWDownloadInfo (88 bytes) 451 | class AWLegacyDownloadItem < BinData::Record 452 | endian :little 453 | string :classname, :read_length => 12, :trim_padding => true, :initial_value => "DISK" 454 | string :name, :read_length => 12, :trim_padding => true 455 | uint32 :address_high, :initial_value => 0 456 | uint32 :address_low 457 | uint32 :lenhi, :initial_value => 0 458 | uint32 :lenlo 459 | string :part, :read_length => 12, :trim_padding => true 460 | string :filename, :read_length => 16, :trim_padding => true 461 | string :verify_filename, :read_length => 16, :trim_padding => true # checksum of the item 462 | uint32 :encrypt, :initial_value => 0 463 | end 464 | 465 | # Unified Structure for dlinfo.fex (16 384 bytes) 466 | class AWDownloadInfo < BinData::Record 467 | uint32le :crc, :value => lambda { 468 | feed = version.to_binary_s << magic << item_count.to_binary_s 469 | feed << stamp.to_binary_s if magic == "softw411" 470 | feed << item.to_binary_s 471 | feed << reserved.to_binary_s if magic == "softw411" 472 | Crc32.calculate(feed, self.num_bytes-4, 0) 473 | } 474 | uint32le :version, :initial_value => 0x200 475 | string :magic, :read_length => 8, :initial_value => "softw411", 476 | :assert => lambda { ["softw311", "softw411"].include? magic } 477 | uint32le :item_count, :value => lambda { item.select { |p| not p.name.empty? }.count } 478 | array :stamp, :type => :uint32le, :initial_length => 3, :onlyif => 479 | lambda { magic == "softw411" } 480 | choice :item, :selection => lambda { magic.to_s } do 481 | array "softw411", :type => :aw_download_item, :initial_length => 120 482 | array "softw311", :type => :aw_legacy_download_item, :initial_length => 15 483 | end 484 | string :reserved, :read_length => 7712, :onlyif => lambda { magic == "softw411"}, 485 | :trim_padding => true 486 | 487 | # Decode dl_info.fex 488 | def inspect 489 | self.each_pair do |k ,v| 490 | print "%-40s" % k.to_s.yellow unless k == :item 491 | case k 492 | when :item 493 | v.each do |item| 494 | next if item.name.empty? 495 | item.each_pair do |i, j| 496 | print "%-40s" % i.to_s.yellow 497 | puts j 498 | end 499 | end 500 | when :stamp then p v 501 | when :crc, :version then puts "0x%08x" % v 502 | else 503 | puts v 504 | end 505 | end 506 | end 507 | 508 | end 509 | 510 | # Livesuit's image item (1024 bytes) 511 | class AWImageItemV1 < BinData::Record 512 | endian :little 513 | uint32 :version, :asserted_value => 0x100 514 | uint32 :item_size 515 | string :main_type, :read_length => 8, :initial_value => "COMMON", :pad_byte => ' ' 516 | string :sub_type, :read_length => 16, :pad_byte => '0' 517 | uint32 :attributes 518 | uint32 :data_len_low 519 | uint32 :file_len_low 520 | uint32 :off_len_low 521 | uint32 :unk 522 | string :path, :read_length => 256, :trim_padding => true 523 | string :reserved, :read_length => 716, :trim_padding => true 524 | hide :reserved 525 | 526 | # Useful to see item data 527 | def inspect 528 | "%-40s @ 0x%08x [%d kB] => %s" % [self.path.yellow, off_len_low, data_len_low>>10, main_type] 529 | end 530 | 531 | end 532 | 533 | # Livesuit's image item (1024 bytes) 534 | class AWImageItemV3 < BinData::Record 535 | endian :little 536 | uint32 :version, :asserted_value => 0x100 537 | uint32 :item_size 538 | string :main_type, :read_length => 8, :initial_value => "COMMON", :pad_byte => ' ' 539 | string :sub_type, :read_length => 16, :pad_byte => '0' 540 | uint32 :attributes 541 | string :path, :read_length => 256, :trim_padding => true 542 | uint32 :data_len_low 543 | uint32 :data_len_hi 544 | uint32 :file_len_low 545 | uint32 :file_len_hi 546 | uint32 :off_len_low 547 | uint32 :off_len_hi 548 | array :encrypt_id, :type => :uint8, :initial_length => 64, :value => 0 549 | uint32 :crc 550 | string :reserved, :read_length => 640, :trim_padding => true 551 | hide :reserved 552 | 553 | # Useful to see item data 554 | def inspect 555 | "%-40s @ 0x%08x [%d kB] => %s" % [self.path.yellow, off_len_low, data_len_low>>10, main_type] 556 | end 557 | 558 | end 559 | 560 | # Livesuit image file header (version 0x100) 561 | # @todo check that 562 | class AWImageHeaderV1 < BinData::Record 563 | endian :little 564 | uint32 :header_size, :asserted_value => 0x50 # size of header-reserved 565 | uint32 :attributes 566 | uint32 :image_version 567 | uint32 :len_low 568 | uint32 :align 569 | uint32 :pid 570 | uint32 :vid 571 | uint32 :hw 572 | uint32 :fw 573 | uint32 :image_attr 574 | uint32 :item_size 575 | uint32 :item_count 576 | uint32 :item_offset 577 | uint32 :item_attr 578 | uint32 :append_size # additional data length 579 | uint32 :append_offset_lo 580 | uint32 :append_offset_hi 581 | string :reserved, :read_length => 944, :trim_padding => true # need to confirm that 582 | hide :reserved 583 | end 584 | 585 | # Livesuit image file header (version 0x300), (1024 bytes) 586 | class AWImageHeaderV3 < BinData::Record 587 | endian :little 588 | uint32 :header_size, :asserted_value => 0x60 # size of header-reserved 589 | uint32 :attributes, :initial_value => 0x4D00000 # disable compression 590 | uint32 :image_version, :initial_value => 0x100234 591 | uint32 :len_low # file size 592 | uint32 :len_hi, :initial_value => 0 593 | uint32 :align, :initial_value => 1024 594 | uint32 :pid, :initial_value => 0x1234 595 | uint32 :vid, :initial_value => 0x8743 596 | uint32 :hw, :initial_value => 256 597 | uint32 :fw, :initial_value => 256 598 | uint32 :image_attr 599 | uint32 :item_size, :initial_value => 1024 # size of AWImageItem 600 | uint32 :item_count # number of AWImageItem embedded in image 601 | uint32 :item_offset, :initial_value => 1024 # item table offset (header is 1024) 602 | uint32 :item_attr 603 | uint32 :append_size # additional data length 604 | uint32 :append_offset_lo 605 | uint32 :append_offset_hi 606 | uint32 :unk1 607 | uint32 :unk2 608 | uint32 :unk3 609 | string :reserved, :read_length => 928, :trim_padding => true 610 | hide :reserved 611 | end 612 | 613 | # Unified Livesuit image structure 614 | class AWImage < BinData::Record 615 | endian :little 616 | string :magic, :read_length => 8, :asserted_value => FELIX_IMG_HEADER 617 | uint32 :image_format, :initial_value => 0x300 618 | choice :header, :selection => :image_format do 619 | aw_image_header_v1 0x100 620 | aw_image_header_v3 0x300 621 | end 622 | choice :item, :selection => :image_format do 623 | array 0x100, :type => :aw_image_item_v1, :initial_length => lambda { header.item_count } 624 | array 0x300, :type => :aw_image_item_v3, :initial_length => lambda { header.item_count } 625 | end 626 | 627 | # Useful to see image data 628 | def inspect 629 | out = "" 630 | self.each_pair do |k ,v| 631 | out << "%-40s" % k.to_s.yellow unless [:header, :item, :magic].include? k 632 | case k 633 | when :image_format then out << "0x%03x\n" % v 634 | when :header 635 | v.each_pair do |i, j| 636 | out << "%-40s%s\n" % [i.to_s.yellow, j] if i == :item_count 637 | out << "%-40s%d MB\n" % [i.to_s.yellow, j>> 20] if i == :len_low 638 | # out "res:" << reserved.inspect if i == :reserved 639 | end 640 | when :item 641 | out << "Items\n".light_blue 642 | v.each { |it| out << it.inspect << "\n" } 643 | end 644 | end 645 | out 646 | end 647 | 648 | # Get item from LiveSuit image by file name 649 | # @param filename [String] item name without path (i.e. system.fex, u-boot.fex, ...) 650 | # @return [AWImageItemV1, AWImageItemV3, nil] first item if found, else nil 651 | def item_by_file(filename) 652 | item = self.item.select do |it| 653 | it.path.match(/.*(?:\\|\/|^)(.+)$/)[1] == filename 654 | end 655 | item.first if item 656 | end 657 | 658 | # Get item from LiveSuit image by signature 659 | # @param signature [String] signature (i.e. BOOTLOADER_FEX00, BOOT_FEX00000000, ...) 660 | # @return [AWImageItemV1, AWImageItemV3, nil] first item if found, else nil 661 | def item_by_sign(signature) 662 | item = self.item.select do |it| 663 | it.sub_type == signature 664 | end 665 | item.first if item 666 | end 667 | 668 | end 669 | 670 | # Sunxi bootloader header 671 | class BootHeader < BinData::Record 672 | endian :little 673 | uint32 :jump_instruction # one intruction jumping to real code 674 | string :magic, :read_length => 8, :trim_padding => true, 675 | :assert => lambda { ["eGON.BT0", "eGON.BT1", "uboot"].include? magic } 676 | uint32 :check_sum 677 | uint32 :align_size # 0x4000 678 | uint32 :file_length # including sys_config.fex & header 679 | uint32 :sys_config_offset # file offset wher sys_config.fex starts 680 | 681 | end 682 | 683 | # A placeholder for uboot crc update 684 | class UbootBinary < BinData::Record 685 | endian :little 686 | boot_header :header 687 | array :uboot, :type => :uint8, :read_until => :eof 688 | end 689 | -------------------------------------------------------------------------------- /FELSuit.rb: -------------------------------------------------------------------------------- 1 | # FELSuit.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | raise "Use ./felix to execute program!" if File.basename($0) == File.basename(__FILE__) 18 | 19 | class FELix 20 | end 21 | 22 | # Contains methods to emulate LiveSuit 23 | class FELSuit < FELix 24 | 25 | attr_reader :structure 26 | 27 | # Initialize device and check image file 28 | # @param device [LIBUSB::Device] a device 29 | # @param file [String] LiveSuit image 30 | def initialize(device, file) 31 | super(device) 32 | @image = file 33 | raise FELError, "Image not found!" unless File.exist?(@image) 34 | @encrypted = encrypted? 35 | @structure = fetch_image_structure 36 | end 37 | 38 | # Write with help of magic_XX_start.fex and magic_xx_end.fex 39 | # @param prefix [Symbol] magic prefix (`cr` or `de`) 40 | # @param data [String] binary data to write 41 | # @param address [Integer] place to write 42 | # @param index [Symbol] one of index (default `:dram`) 43 | # @raise [FELError] if failed 44 | # @note (Probably) Only usable in legacy images 45 | def magic_write(prefix, data, address, index = :dram) 46 | start = get_image_data(@structure.item_by_file("magic_#{prefix}_start.fex")) 47 | finish = get_image_data(@structure.item_by_file("magic_#{prefix}_end.fex")) 48 | 49 | transmit(:write, :address => 0x40330000, :memory => start) 50 | transmit(:write, :address => address, :memory => data, :media_index => index) 51 | transmit(:write, :address => 0x40330000, :memory => finish) 52 | rescue FELError => e 53 | raise FELError, "Failed to transmit with magic (#{e})" 54 | end 55 | 56 | # Flash legacy image to the device 57 | # @raise [FELError, FELFatal] if failed 58 | # @param format [TrueClass, FalseClass] force storage format 59 | # @yieldparam [String] status 60 | # @yieldparam [Symbol] message type (:info, :percent, :action) 61 | # @yieldparam [Integer] message argument 62 | def flash_legacy(format = false) 63 | # Temporary fallback 64 | raise FELFatal, "That functionality isn't finished yet!" 65 | raise FELFatal, "Tried to flash legacy file that isn't legacy!" unless legacy? 66 | # 1. Let's check device mode 67 | info = get_device_status 68 | raise FELError, "Failed to get device info. Try to reboot!" unless info 69 | # 2. If we're in FEL mode we must firstly boot2fes 70 | if info.mode == AWDeviceMode[:fel] 71 | fes11 = get_image_data(@structure.item_by_file("fes_1-1.fex")) 72 | fes12 = get_image_data(@structure.item_by_file("fes_1-2.fex")) 73 | fes = get_image_data(@structure.item_by_file("fes.fex")) 74 | fes2 = get_image_data(@structure.item_by_file("fes_2.fex")) 75 | yield "Booting to FES" if block_given? 76 | boot_to_fes_legacy(fes11, fes12, fes, fes2) 77 | yield "Waiting for reconnection" if block_given? 78 | # 3. Wait for device reconnection 79 | sleep(5) 80 | raise FELError, "Failed to reconnect!" unless reconnect? 81 | info = get_device_status 82 | end 83 | # 4. Generate and send SYS_PARA 84 | cfg = "" 85 | leg = false 86 | if @structure.item_by_file("sys_config1.fex") 87 | cfg = get_image_data(@structure.item_by_file("sys_config1.fex")) 88 | cfg << get_image_data(@structure.item_by_file("sys_config.fex")) 89 | leg = true 90 | else 91 | cfg = get_image_data(@structure.item_by_file("sys_config.fex")) 92 | end 93 | 94 | sys_para = AWSysPara.new 95 | sys_para.dram = FELHelpers.create_dram_config(cfg, leg) 96 | 97 | mbr = AWMBR.read(get_image_data(@structure.item_by_file("sunxi_mbr.fex"))) 98 | sys_para.part_num = mbr.mbr.part_count 99 | mbr.mbr.part.each_with_index do |item, n| 100 | sys_para.part_items[n] = AWSysParaPart.new(item) 101 | end 102 | 103 | dlinfo = AWDownloadInfo.read(get_image_data(@structure.item_by_file("dlinfo.fex"))) 104 | sys_para.dl_num = dlinfo.item_count 105 | dlinfo.item.each_with_index do |item, n| 106 | sys_para.dl_items[n] = AWSysParaItem.new(item) 107 | end 108 | 109 | yield "Sending DRAM config" if block_given? 110 | transmit(:write, :address => 0x40900000, :memory => sys_para.to_binary_s) 111 | yield "Writing FED" if block_given? 112 | magic_write(:de, get_image_data(@structure.item_by_file("fed_nand.axf")), 113 | 0x40430000) 114 | yield "Starting FED" if block_given? 115 | run(0x40430000, :fes, [:fed, :has_param], [0x40900000, 0x40901000, 0, 0]) 116 | 117 | end 118 | 119 | # Flash image to the device 120 | # @raise [FELError, FELFatal] if failed 121 | # @param format [TrueClass, FalseClass] force storage format 122 | # @param verify [TrueClass, FalseClass] verify written data 123 | # @param method [Symbol] protocol version (`:v1` or `:v2`) 124 | # @yieldparam [String] status 125 | # @yieldparam [Symbol] message type (:info, :percent, :action) 126 | # @yieldparam [Integer] message argument 127 | def flash(format = false, verify = true, method = :v1) 128 | return flash_legacy(format) {|i| yield i} if legacy? 129 | # 1. Let's check device mode 130 | info = get_device_status 131 | raise FELError, "Failed to get device info. Try to reboot!" unless info 132 | # 2. If we're in FEL mode we must firstly boot2fes 133 | uboot = get_image_data(@structure.item_by_file("u-boot.fex")) 134 | fes = get_image_data(@structure.item_by_file("fes1.fex")) 135 | if info.mode == AWDeviceMode[:fel] 136 | yield "Booting to FES" if block_given? 137 | boot_to_fes(fes, uboot) 138 | yield "Waiting for reconnection" if block_given? 139 | # 3. Wait for device reconnection 140 | sleep(5) 141 | raise FELError, "Failed to reconnect!" unless reconnect? 142 | info = get_device_status 143 | end 144 | raise FELError, "Failed to boot to fes" unless info.mode == AWDeviceMode[:fes] 145 | # 4. Write MBR 146 | yield "Writing new paratition table" << (format ? " and formatting storage" : 147 | "") if block_given? 148 | mbr = get_image_data(@structure.item_by_file("sunxi_mbr.fex")) 149 | dlinfo = AWDownloadInfo.read(get_image_data(@structure.item_by_file( 150 | "dlinfo.fex"))) 151 | time = Time.now unless format 152 | status = write_mbr(mbr, format) 153 | # HACK: If writing operation took more than 15 seconds assume device is formated 154 | if !format && (Time.now - time) > 15 then 155 | yield ("Warning:".red << " Storage has been formatted anyway!"), :info if block_given? 156 | format = true 157 | end 158 | raise FELError, "Cannot flash new partition table" if status.crc != 0 159 | # 5. Enable NAND / SDCARD access 160 | yield "Enabling the storage access" if block_given? 161 | set_storage_state(:on) 162 | # 6. Write partitions 163 | dlinfo.item.each do |item| 164 | break if item.name.empty? 165 | sparsed = false 166 | # Don't write udisk 167 | # @todo add support for not empty udisk image 168 | next if item.name == "UDISK" 169 | part = @structure.item_by_sign(item.filename) 170 | raise FELError, "Cannot find item: #{item.filename} in the " << 171 | "image" unless part 172 | 173 | # Check if the current item is a sparse image 174 | sparsed = SparseImage.is_valid?(get_image_data(part, 64)) 175 | 176 | # Check CRC of the image if it's the same - no need to spam NAND with the same data 177 | # This should speed up flashing process A LOT 178 | # But if format flag is set that's just waste of time 179 | unless format || item.verify_filename.empty? || sparsed then 180 | yield "Checking #{item.name}" if block_given? 181 | crc = verify_value(item.address_low, part.data_len_low) 182 | crc_item = @structure.item_by_sign("V" << item.filename[0...-1]) 183 | valid_crc = get_image_data(crc_item).unpack("V")[0] 184 | if crc.crc == valid_crc then 185 | yield "#{item.name} is unchanged. Skipping...", :info if block_given? 186 | next 187 | else 188 | yield "#{item.name} needs to be reflashed! (#{crc.crc} !=" << 189 | " #{valid_crc})", :info if block_given? 190 | end 191 | end 192 | yield "Flashing #{item.name}" if block_given? 193 | curr_add = item.address_low 194 | if sparsed 195 | sys_handle = get_image_handle(part) 196 | sparse = SparseImage.new(sys_handle, part.off_len_low) 197 | # @todo 198 | # 4096 % 512 == 0 so it shouldn't be a problem 199 | # but 4096 / 65536 it is 200 | queue = Queue.new 201 | len = 0 202 | threads = [] 203 | threads << Thread.new do 204 | i = 1 205 | 206 | sparse.each_chunk do |data, type| 207 | len += data.length 208 | yield ("Decompressing sparse image #{item.name}"), :percent, (i * 100) / sparse. 209 | count_chunks if block_given? 210 | queue << [data, type] 211 | i+=1 212 | # Optimize memory usage (try to keep heap at 128MB) 213 | while len > (128 << 20) && !queue.empty? 214 | sleep 0.5 215 | end 216 | end 217 | sys_handle.close 218 | end 219 | threads << Thread.new do 220 | written = 0 221 | sparse.count_chunks.times do |chunk_num| 222 | data, chunk_type = queue.pop 223 | len -= data.length 224 | written+=data.bytesize 225 | 226 | # @todo Finish block should be always set if next block is :dont_care 227 | # according to packet log 228 | finish = (chunk_num == sparse.count_chunks - 1) || sparse.chunks[ 229 | chunk_num + 1].chunk_type == ChunkType[:dont_care] && (chunk_num == 230 | sparse.count_chunks - 2) 231 | write(curr_add, data, :none, :fes, !finish, method: method) do |ch| 232 | yield ("Writing #{item.name} @ 0x%08x" % (curr_add + (ch / 512))), 233 | :percent, ((written - data.bytesize + ch) * 100) / sparse. 234 | get_final_size if block_given? 235 | end 236 | break if finish 237 | curr_add+=data.bytesize / FELIX_SECTOR 238 | end 239 | yield "Writing #{item.name}", :percent, 100 if block_given? 240 | end 241 | threads.each {|t| t.join} 242 | else 243 | queue = Queue.new 244 | threads = [] 245 | len = 0 246 | data_size = part.data_len_low 247 | # reader 248 | threads << Thread.new do 249 | read = 0 250 | if item.name == "sysrecovery" 251 | # sysrecovery is a partition that consist the flashed image 252 | data_size = File.size(@image) 253 | File.open(@image, "rb") do |f| 254 | while not f.eof? 255 | chunk = f.read(FELIX_MAX_CHUNK) 256 | read+=chunk.bytesize 257 | len+=chunk.bytesize 258 | yield "Reading #{item.name}", :percent, (read * 100) / data_size if block_given? 259 | queue << chunk 260 | while len > (128 << 20) && !queue.empty? 261 | sleep 0.5 262 | end 263 | end 264 | end 265 | else 266 | get_image_data(part) do |data| 267 | read+=data.bytesize 268 | len+=data.bytesize 269 | yield "Reading #{item.name}", :percent, (read * 100) / data_size if block_given? 270 | queue << data 271 | while len > (128 << 20) && !queue.empty? 272 | sleep 0.5 273 | end 274 | end 275 | end 276 | end 277 | # writter 278 | threads << Thread.new do 279 | written = 0 280 | while written < data_size 281 | data = queue.pop 282 | written+=data.bytesize 283 | len-=data.bytesize 284 | write(curr_add, data, :none, :fes, written < data_size, method: method) do 285 | yield "Writing #{item.name}", :percent, (written * 100) / data_size if block_given? 286 | end 287 | curr_add+=(data.bytesize / FELIX_SECTOR) 288 | end 289 | yield "Writing #{item.name}", :percent, 100 if block_given? 290 | end 291 | threads.each {|t| t.join} 292 | end 293 | # Verify CRC of written data 294 | if verify && item.name != "UDISK" && !sparsed then 295 | # @todo Check why system partition's CRC is not matching 296 | yield "Verifying #{item.name}" if block_given? 297 | crc = verify_value(item.address_low, part.data_len_low) 298 | crc_item = @structure.item_by_sign("V" << item.filename[0...-1]) 299 | if crc_item 300 | valid_crc = get_image_data(crc_item) 301 | # try again if verification failed 302 | if crc.crc != valid_crc.unpack("V")[0] then 303 | yield "CRC mismatch for #{item.name}: (#{crc.crc} !=" << 304 | " #{valid_crc.unpack("V")[0]}). Trying again...", :info if block_given? 305 | redo 306 | end 307 | else 308 | yield "SKIP", :warn if block_given? 309 | end 310 | end 311 | end 312 | # 7. Disable storage 313 | yield "Disabling the storage access" if block_given? 314 | set_storage_state(:off) 315 | # 8. Write u-boot 316 | # @todo toc1.fex & toc0.fex should be written if secure flag is set 317 | yield "Writing u-boot" if block_given? 318 | write(0, uboot, :uboot, :fes, method: method) do |n| 319 | yield "Writing u-boot", :percent, (n * 100) / uboot.bytesize if block_given? 320 | end 321 | yield "Veryfing u-boot" if block_given? 322 | resp = verify_status(:uboot) 323 | raise FELFatal, "Failed to update u-boot" if resp.crc != 0 324 | # 9. Write boot0 325 | yield "Checking the storage type" if block_given? 326 | storage = query_storage 327 | boot0_file = String.new 328 | case storage 329 | when :nand 330 | boot0_file = "boot0_nand.fex" 331 | when :card, :card2 332 | boot0_file = "boot0_sdcard.fex" 333 | when :spinor 334 | boot0_file = "boot0_spinor.fex" 335 | else 336 | raise FELFatal, "Unknown storage type (#{storage})" 337 | end 338 | boot0 = get_image_data(@structure.item_by_file(boot0_file)) 339 | yield "Writing boot0" if block_given? 340 | write(0, boot0, :boot0, :fes, method: method) do |n| 341 | yield "Writing boot0", :percent, (n * 100) / boot0.bytesize if block_given? 342 | end 343 | yield "Veryfing boot0" if block_given? 344 | resp = verify_status(:boot0) 345 | raise FELFatal, "Failed to update boot0" if resp.crc != 0 346 | # 10. Reboot 347 | yield "Rebooting" if block_given? 348 | set_tool_mode(:usb_tool_update, :none) 349 | yield "Finished", :info if block_given? 350 | end 351 | 352 | # Download egon, uboot and run code in hope we boot to fes 353 | # @param egon [String] FES binary data (init dram code, eGON, fes1.fex) 354 | # @param uboot [String] U-boot binary data (u-boot.fex) 355 | # @param mode [Symbol] desired work mode 356 | # @todo Verify header (eGON.BT0, uboot) 357 | # @raise [FELError] 358 | def boot_to_fes(egon, uboot, mode = :usb_product) 359 | raise FELError, "Unknown work mode (#{mode.to_s})" unless AWUBootWorkMode[mode] 360 | raise FELError, "eGON is too big (#{egon.bytesize}>16384)" if egon.bytesize>16384 361 | write(0x2000, egon) 362 | run(0x2000) 363 | write(0x4a000000, uboot) 364 | write(0x4a0000e0, AWUBootWorkMode[mode].chr) unless mode == :boot 365 | run(0x4a000000) 366 | end 367 | 368 | # Download fes* files and run code in hope we boot to fes 369 | # @param fes [String] fes_1-1.fex binary 370 | # @param fes_next [String] fes_1-2.fex binary 371 | # @param fes2 [String] fes.fex binary 372 | # @param fes2_next [String] fes_2.fex binary 373 | # @param dram_cfg [AWSystemParameters] DRAM params (recreated from sys_config if empty) 374 | # @raise [FELError] 375 | # @note Only for legacy images 376 | def boot_to_fes_legacy(fes, fes_next, fes2, fes2_next, dram_cfg = nil) 377 | raise FELError, "FES1-1 is too big (#{fes.bytesize}>2784)" if fes. 378 | bytesize>2784 379 | raise FELError, "FES1-2 is too big (#{fes_next.bytesize}>16384)" if fes_next. 380 | bytesize>16384 381 | raise FELError, "FES2 is too big (#{fes2.bytesize}>0x80000)" if fes2. 382 | bytesize>0x80000 383 | raise FELError, "FES2-2 is too big (#{fes2_next.bytesize}>2784)" if fes2_next. 384 | bytesize>2784 385 | raise FELError, "sys_config.fex not found. Make sure it's legacy image type" unless 386 | @structure.item_by_file("sys_config.fex") 387 | 388 | # Create DRAM config based on sys_config.fex if doesn't exist 389 | unless dram_cfg 390 | cfg = "" 391 | leg = false 392 | if @structure.item_by_file("sys_config1.fex") 393 | cfg = get_image_data(@structure.item_by_file("sys_config1.fex")) 394 | cfg << get_image_data(@structure.item_by_file("sys_config.fex")) 395 | leg = true 396 | else 397 | cfg = get_image_data(@structure.item_by_file("sys_config.fex")) 398 | end 399 | dram_cfg = FELHelpers.create_dram_config(cfg, leg) 400 | end 401 | #dram_cfg.dram_size = 0 402 | 403 | #write(0x7e00, "\0"*4 << "\xCC"*252) 404 | 405 | write(0x7010, dram_cfg.to_binary_s) 406 | #write(0x7210, "\0"*16) # clear LOG 407 | #if fes.bytesize<2784 408 | # fes << "\0" * (2784 - fes.bytesize) 409 | #end 410 | write(0x7220, fes) 411 | run(0x7220) 412 | sleep(2) 413 | # data = read(0x7210, 16) 414 | # p data if $options[:verbose] 415 | # write(0x7210, "\0"*16) # clear LOG 416 | 417 | write(0x2000, fes_next) 418 | run(0x2000) 419 | write(0x40200000, fes2) 420 | write(0x7220, fes2_next) 421 | run(0x7220) 422 | end 423 | 424 | # Check if image is encrypted 425 | # @raise [FELError] if image decryption failed 426 | def encrypted? 427 | img = File.read(@image, 16, mode: "rb") # Read block 428 | return false if img.byteslice(0, 8) == FELIX_IMG_HEADER 429 | img = FELHelpers.decrypt(img, :header) if img.byteslice(0, 8) != 430 | FELIX_IMG_HEADER 431 | return true if img.byteslice(0, 8) == FELIX_IMG_HEADER 432 | raise FELError, "Failed to decrypt image" 433 | end 434 | 435 | # Check image format 436 | def legacy? 437 | not (@structure.item_by_file("u-boot.fex") && @structure.item_by_file( 438 | "fes1.fex")) 439 | end 440 | 441 | # Read item data from LiveSuit image 442 | # @param item [AWImageItemV1, AWImageItemV3] item data 443 | # @param chunk [Integer] size of yielded chunk 444 | # @param length [Integer] how much data to read 445 | # @param offset [Integer] where to start reading of data 446 | # @return [String] binary data if no block given 447 | # @yieldparam [String] data 448 | # @raise [FELError] if read failed 449 | def get_image_data(item, chunk = FELIX_MAX_CHUNK, length = item.data_len_low, 450 | offset = 0) 451 | raise FELError, "Item does not exist" unless item 452 | if block_given? 453 | File.open(@image, "rb") do |f| 454 | f.seek(item.off_len_low + offset, IO::SEEK_CUR) 455 | read = 0 456 | while data = f.read(chunk) 457 | data = FELHelpers.decrypt(data, :data) if @encrypted 458 | read+=data.bytesize 459 | left = read - length 460 | if left > 0 461 | yield data.byteslice(0, data.bytesize - left) 462 | break 463 | else 464 | yield data 465 | break if read == length 466 | end 467 | end 468 | end 469 | else 470 | data = File.read(@image, length, item.off_len_low + offset, mode: "rb") 471 | raise FELError, "Cannot read data" unless data 472 | data = FELHelpers.decrypt(data, :data) if @encrypted 473 | data 474 | end 475 | end 476 | 477 | # Seeks to image position and get file handle 478 | # @param item [AWImageItemV1, AWImageItemV3] item data 479 | # @return [File] handle 480 | # @raise [FELError] if failed 481 | # @note Don't forget to close handle after 482 | def get_image_handle(item) 483 | raise FELError, "Item not exist" unless item 484 | f = File.open(@image, "rb") 485 | f.seek(item.off_len_low, IO::SEEK_CUR) 486 | f 487 | end 488 | 489 | # Read header & image items information 490 | # @return [AWImage] LiveSuit image structure 491 | def fetch_image_structure 492 | if @encrypted 493 | File.open(@image, "rb") do |f| 494 | header = f.read(1024) 495 | header = FELHelpers.decrypt(header, :header) 496 | img_version = header[8, 4].unpack("V").first 497 | item_count = header[img_version == 0x100 ? 0x38 : 0x3C, 4]. 498 | unpack("V").first 499 | items = f.read(item_count * 1024) 500 | header << FELHelpers.decrypt(items, :item) 501 | AWImage.read(header) 502 | end 503 | else 504 | # much faster if image is not encrypted 505 | File.open(@image, "rb") { |f| AWImage.read(f) } 506 | end 507 | end 508 | 509 | end 510 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby "2.3.3" 3 | 4 | gem 'hex_string' 5 | gem 'hexdump' 6 | gem 'colorize' 7 | gem 'libusb' 8 | gem 'bindata', '2.4.0' 9 | gem 'crc32' 10 | gem 'inifile' 11 | gem 'rc6' 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FELix 2 | ================== 3 | 4 | FELix is a multiplatform tool for Allwinner processors handling FEL and FES 5 | protocol written in Ruby 6 | 7 | * Uses libusb1.0 / ruby 2.0+ 8 | * More powerful than fel tool from sunxi-tools 9 | 10 | Features 11 | ------------------ 12 | 13 | * Write / read memory 14 | * Execute the code at address 15 | * Flash LiveSuit image (at this moment only newer image are supported) 16 | * Extract single item from LiveSuit image 17 | * Format the device NAND / Write new MBR 18 | * Enable NAND 19 | * Dump/flash single partition 20 | * Display the device info 21 | * Reboot the device 22 | * Boot device using given u-boot 23 | 24 | Installation 25 | ------------------ 26 | 27 | 1. Install ruby 2.0+ (you can use ruby-installer on Windows) 28 | 29 | $ sudo apt-get install ruby2.0 ruby2.0-dev 30 | $ sudo ln -sf /usr/bin/ruby2.0 /usr/bin/ruby 31 | $ sudo ln -sf /usr/bin/gem2.0 /usr/bin/gem 32 | 33 | 2. Install bundler 34 | 35 | $ gem install bundler 36 | 37 | 3. Install libraries (Linux only) 38 | 39 | $ sudo apt-get install libusb-1.0.0-dev libffi-dev 40 | 41 | 4. Run bundler in application directory (You may need to edit Gemfile to match your ruby version) 42 | 43 | $ bundle 44 | 45 | 5. Switch to FEL mode (`adb reboot efex`) and install a usb filter (Windows only) 46 | over the default USB driver. Use [Zadig](http://zadig.akeo.ie/). 47 | 48 | 49 | Usage 50 | ------------------ 51 | 52 | See `(ruby) felix --help` for available commands 53 | 54 | 55 | Howtos 56 | ------------------ 57 | 58 | * Dump/flash single partition 59 | 60 | 1. Boot to FES 61 | 62 | $ felix --tofes 63 | 64 | 2. Enable NAND 65 | 66 | $ felix --nand on 67 | 68 | 3. Flash or dump partition 69 | 70 | $ felix --write boot.img --item boot 71 | $ felix --read boot.img --item boot 72 | 73 | 4. Disable NAND 74 | 75 | $ felix --nand off 76 | 77 | 78 | * Write new `boot0`/`boot1` (**Warning**: this may brick your device if you write incorrect file) 79 | 80 | 1. Boot to FES 81 | 82 | $ felix --tofes 83 | 84 | 2. Write new boot0 using fes context and boot0 tag 85 | 86 | $ felix --write boot0_nand.fex -c fes -t boot0 -a 0 (for boot1 use boot1 or uboot tag) 87 | 88 | 3. Optionally reboot device 89 | 90 | $ felix --reboot 91 | 92 | 93 | Issues 94 | ------------------ 95 | 96 | As I have limited access to Allwinner devices, I encourage you to report issues 97 | you encounter in Issues section. As far I tested the tool on A13, A23, A31, A31s and A83. 98 | 99 | 100 | Todo 101 | ------------------ 102 | 103 | There's a lot of things to do. The most important are: 104 | 105 | - [ ] Support for legacy image format (partially done) 106 | - [x] Boot to FES 107 | - [ ] Flash legacy image 108 | - [x] Extract legacy image 109 | - [x] Validation of files before flash 110 | - [ ] Improve error handling (may be troublesome) 111 | - [x] Separate command for reading/writing NAND partitions 112 | - [x] Improve speed of libsparse / rc6 algorithm 113 | - [ ] Partitioning support without sunxi_mbr 114 | - [ ] Handle every available FEL/FES command 115 | - [ ] Some kind of GUI 116 | -------------------------------------------------------------------------------- /felix: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # FELix.rb 3 | # Copyright 2014-2015 Bartosz Jankowski 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | require 'hex_string' 18 | require 'hexdump' 19 | require 'colorize' 20 | require 'optparse' 21 | require 'libusb' 22 | require 'bindata' 23 | require 'crc32' 24 | require 'rc6' 25 | require 'thread' 26 | require 'inifile' 27 | require 'fileutils' 28 | require 'stringio' 29 | 30 | require_relative 'FELCmds' 31 | require_relative 'FELConsts' 32 | require_relative 'FELStructs' 33 | require_relative 'FELHelpers' 34 | require_relative 'FELSuit' 35 | require_relative 'libsparse' 36 | 37 | # @example Routines V1 (should be working on every device) 38 | # 1. Write (--> send | <-- recv) 39 | # --> AWUSBRequest(AW_USB_WRITE, len) 40 | # --> WRITE(cmd, len) 41 | # <-- READ(13) -> AWUSBResponse 42 | # (then) 43 | # 2. Read a response or write the data 44 | # --> AWUSBRequest(AW_USB_READ, len) | --> AWUSBRequest(AW_USB_WRITE, len) 45 | # <-- READ(len) | --> WRITE(data, len) 46 | # <-- READ(13) -> AWUSBResponse | <-- READ(13) -> AWUSBResponse 47 | # (then) 48 | # 3. Read a status 49 | # --> AWUSBRequest(AW_USB_READ, 8) 50 | # <-- READ(8) -> AWFELStatusResponse 51 | # <-- READ(13) -> AWUSBResponse 52 | # 53 | # @example Routines V2 (appeared on A83, now you can use AWUSBRequestV2 for all commands) 54 | # => Write (--> send | <-- recv) 55 | # --> AWUSBRequestV2(cmd, address, len, flags) 56 | # --> WRITE(data, len) 57 | # <-- READ(13) -> AWUSBResponse 58 | # (or) 59 | # => Read 60 | # --> AWUSBRequestV2(cmd, address, len, flags) 61 | # <-- READ(len) 62 | # <-- READ(13) -> AWUSBResponse 63 | # 64 | # @example Some important info about memory layout. Treat ranges as [a..b-1] 65 | # 0x0: SRAM_BASE 66 | # 0x2000 - 0x6000: INIT_CODE (16384 bytes), also: DRAM_INIT_CODE_ADDR 67 | # 0x7010 - 0x7D00: FEL_MEMORY (3312 bytes), also: FEL_RESERVE_START 68 | # => 0x7010 - 0x7210: SYS_PARA (512 bytes) 69 | # => 0x7210 - 0x7220: SYS_PARA_LOG_ADDR (16 bytes) 70 | # => 0x7220 - 0x7D00: SYS_INIT_PROC_ADDR (2784 bytes) 71 | # 0x7D00 - 0x7E00: ? (256 bytes) 72 | # 0x7E00 - ? : DATA_START_ADDR 73 | # 0x40000000: DRAM_BASE 74 | # => 0x40000000 - 0x40008000: FEX_SRAM_A_BASE (32768 bytes) 75 | # => 0x40008000 - 0x40028000: FEX_SRAM_B_BASE (131072 bytes) 76 | # => 0x40023C00: FEX_CRC32_VALID_ADDR (512 bytes) 77 | # => 0x40024000: FEX_SRAM_FES_IRQ_STACK_BASE (8192 bytes) 78 | # => 0x40023E00: FEX_SRAM_FES_PHO_PRIV_BASE (512 bytes) 79 | # => 0x40026000: FEX_SRAM_FET_STACK_BASE (8192 bytes) 80 | # => 0x40028000 - ?: FEX_SRAM_C_BASE 81 | # => 0x40100000: DRAM_TEST_ADDR, FEX_DRAM_BASE 82 | # => 0x40200000 - 0x40280000: FES_ADDR_CRYPTOGRAPH (fes.fex, max 524288 bytes) 83 | # => 0x40280000 - 0x40300000: FES_ADDR_PROCLAIM (524288 bytes) 84 | # => 0x40300000 - 0x40400000: FEX_MISC_RAM_BASE (5242880 bytes) 85 | # => 0x40400000 - 0x40410000: FET_PARA1_ADDR (65536 bytes) 86 | # => 0x40410000 - 0x40420000: FET_PARA2_ADDR (65536 bytes) 87 | # => 0x40420000 - 0x40430000: FET_PARA3_ADDR (65536 bytes) 88 | # => 0x40430000 - 0x40470000: FET_CODE_ADDR (262144 bytes), FED_CODE_DOWN_ADDR (524288 bytes) 89 | # => 0x40600000 - 0x40700000: BOOTX_BIN_ADDR (1048576 bytes) 90 | # => 0x40800000 - 0x40900000: FED_TEMP_BUFFER_ADDR (1048576 bytes) 91 | # => 0x40900000 - 0x40901000: FED_PARA_1_ADDR (4096 bytes) 92 | # => 0x40901000 - 0x40902000: FED_PARA_2_ADDR (4096 bytes) 93 | # => 0x40902000 - 0x40903000: FED_PARA_3_ADDR (4096 bytes) 94 | # (...) 95 | # => 0x4A000000: u-boot.fex 96 | # => 0x4D415244: SYS_PARA_LOG (second instance?) 97 | # => 0x5ffe7f08: MBR [not sure] 98 | # => 0x80600000: FEX_SRAM_FOR_FES_TEMP_BUF (65536 bytes) 99 | # @example Booting to FES (boot 1.0) 100 | # 1. Steps 1-4 of boot 2.0 method 101 | # 2. FEL_DOWNLOAD: Send 512 bytes of data (seems its some failsafe DRAM config 102 | # AWSystemParameters) at 0x7010 (SYS_PARA) 103 | # => 180 bytes for sun4i 104 | # 2a.FEL_DOWNLOAD: Send 16 bytes of data (0x00's) at 0x7210 [sun4i only] 105 | # 3. FEL_DOWNLOAD: Send 2784 bytes of data (fes1-1.fex, padded with 0x00) at 0x7220 (SYS_INIT_PROC) 106 | # => 2784 because that's length of SYS_INIT_PROC 107 | # 4. FEL_RUN: Run code at 0x7220 (fes1-1.fex) 108 | # 5. FEL_UPLOAD: Get 16 bytes of data ("DRAM", rest 0x00) from 0x7210 (SYS_PARA_LOG) 109 | # 6. FEL_DOWNLOAD: Send 16 bytes of data (filed 0x00) at 0x7210 (SYS_PARA_LOG) 110 | # => Clear SYS_PARA_LOG 111 | # 7. FEL_DOWNLOAD: Send 8544 bytes of data (fes1-2.fex) at 0x2000 (INIT_CODE) 112 | # 8. FEL_RUN: Run code at 0x2000 (fes1-2.fex) => inits and sets dram 113 | # 9. FEL_UPLOAD: Get 16 bytes of data ("DRAM",0x00000001, rest 0x00) from 0x7210 (SYS_PARA_LOG) 114 | # => if 1 then DRAM is updated, else "Failed to update dram para" 115 | # 10.FEL_UPLOAD: Get 512 bytes of data (AWSystemParameters) from 0x7010 (SYS_PARA) 116 | # => 180 bytes for sun4i 117 | # 11.FEL_DOWNLOAD: Send 8192 bytes of random generated data at 0x40100000 (DRAM_TEST_ADDR) 118 | # 12.FEL_UPLOAD: Get 8192 bytes of data from 0x40100000 => verify if DRAM is working ok 119 | # 13.FEL_DOWNLOAD: Send 16 bytes of data (filed 0x00) at 0x7210 (SYS_PARA_LOG) 120 | # => Clear SYS_PARA_LOG 121 | # 13.FEL_DOWNLOAD: Send 86312 bytes of data (fes.fex) at 0x40200000 (FES_ADDR_CRYPTOGRAPH) 122 | # 14.FEL_DOWNLOAD: Send 1964 bytes of data (fes_2.fex) at 0x7220 (SYS_INIT_PROC_ADDR) 123 | # 15.FEL_RUN: Run code at 0x7220 (fes_2.fex) 124 | # => mode: fes, you can send FES commands now 125 | # *** Flash tool asks user if he would like to do format or upgrade 126 | # @example Booting to FES (boot 2.0) 127 | # 1. FEL_VERIFY_DEVICE => mode: fel, data_start_address: 0x7E00 128 | # 2. FEL_VERIFY_DEVICE (not sure why it's spamming with this) 129 | # 3. FEL_UPLOAD: Get 256 bytes of data (filed 0xCC) from 0x7E00 (data_start_address) 130 | # 4. FEL_VERIFY_DEVICE 131 | # 5. FEL_DOWNLOAD: Send 256 bytes of data (0x00000000, rest 0xCC) at 0x7E00 (data_start_address) 132 | # 4. FEL_VERIFY_DEVICE 133 | # 5. FEL_DOWNLOAD: Send 16 bytes of data (filed 0x00) at 0x7210 (SYS_PARA_LOG) 134 | # => It's performed to clean FES helper log 135 | # 6. FEL_DOWNLOAD: Send 6496 bytes of data (fes1.fex) at 0x2000 (INIT_CODE) 136 | # 7. FEL_RUN: Run code at 0x2000 (fes1.fex) => inits dram 137 | # 8. FEL_UPLOAD: Get 136 bytes of data (DRAM...) from 0x7210 (SYS_PARA_LOG) 138 | # => After "DRAM" + 0x00000001, there's 32 dword with dram params 139 | # 9. FEL_DOWNLOAD(12 times because u-boot.fex is 0xBC000 bytes): 140 | # => Send (u-boot.fex) 0x4A000000 in 65536 bytes chunks, last chunk is 49152 141 | # => bytes and ideally starts at config.fex data 142 | # => *** VERY IMPORTANT ***: There's set a flag (0x10) at 0xE0 byte of u-boot. 143 | # => Otherwise device will start normally after start of u-boot 144 | # 10.FEL_RUN: Run code at 0x4A000000 (u-boot.fex; its called also fes2) 145 | # => mode: fes, you can send FES commands now 146 | # *** Flash tool asks user if he would like to do format or upgrade 147 | # @example Flash process (A10) (FES) (boot 1.0) 148 | # 1. FEL_VERIFY_DEVICE: Allwinner A31s (sun6i), revision 0, FW: 1, mode: fes 149 | # 2. FES_TRANSMIT (read flag, index:dram): Get 256 of data from 0x7e00 (filed 0xCC) 150 | # 3. FEL_VERIFY_DEVICE: Allwinner A31s (sun6i), revision 0, FW: 1, mode: fes 151 | # These 3 steps above seems optional 152 | # 4. FES_TRANSMIT: (:write, :dram): Send 256 of data at 0x7e00 (0x00000000, rest 0xCC) 153 | # 5. FES_TRANSMIT: (:write, :dram): Send 5496 of data at 0x40900000 154 | # => (SYS_PARA,...) 155 | # 6. Write element: (fed_nand.axf, de, :dram), 129056 bytes at 0x40430000 156 | # 7. FES_RUN: Run code at 0x40430000, max_para => 49 (fed|has_para) 157 | # => PARAMS = [ 0x40900000 => "SYS_PARA...", 0x40901000, 0x0, 0x0] 158 | # => Init NAND 159 | # 8.FES_INFO: => return 0x100,(0x00*) (32 bytes) 160 | # 9.FES_GET_MSG: (address: 0x400) => probably returned data size 161 | # => returns 1024 bytes 162 | # ---------- Writing partition start 163 | # I.FES_TRANSMIT: (:write, :dram): Send 128 bytes of data at 0x40330000 (magic_cr_start.fex) 164 | # II.FES_TRANSMIT: (:write|:start, :nand2) Send bootloader.fex (nanda) at 0x8000 in 65536 chunks, 165 | # but address offset must be divided by 512 => 65536/512 = 128. 166 | # Thus (0x8000, 0x8080, 0x8100, etc), at last chunk :finish context must be set, 167 | # and at first chunk :start context must be set 168 | # III.FES_TRANSMIT: (:write, :dram): Send 128 bytes of data at 0x40330000 (magic_cr_end.fex) 169 | # IV.FES_TRANSMIT: (:read, :dram): Read 12 bytes of data from 0x40023C00 170 | # => AWFESVerifyStatusResponse, optional step 171 | # V.FES_TRANSMIT: (:write, :dram): Send 12 bytes of data from 0x40023C00 172 | # => Clear AWFESVerifyStatusResponse buffer with 0x00s, optional 173 | # ---------- Writing partition end 174 | # 10.Write partition: "env.fex", "boot.fex", "system.fex", "recovery.fex" 175 | # 11.Writing partition: "mbr.fex", additional :start & :finish flag set because its 1 chunk 176 | # 12.FES_UNREG_FED: :address => 0x2 177 | # => disables nand 178 | # ---------- Update boot1 179 | # 13.Write element: boot1.bin, de (see I-III), 376832 bytes at 0x40600000 180 | # 14.Write element: update_boot1.axf, de, 114352 bytes at 0x40430000 181 | # 15.FES_RUN: Run code at 0x40430000, max_para => 17 (fet|has_para) 182 | # => PARAMS = [0x40600000, 0x40400000, 0x40410000, 0x00000000] 183 | # 16.FES_INFO: => return 0x1,(0x00*) (32 bytes) until reponse return 0x100 => success? 184 | # 17.FES_GET_MSG: (address: 0x400) => probably returned data size 185 | # => returns 1024 bytes (...)"updateBootxOk000" 186 | # ---------- End of update boot1 187 | # ---------- Update boot0 188 | # 18.Write element: (boot0.bin,de) 24576 bytes at 0x40600000 189 | # 19.FES_TRANSMIT: (:write, :dram): Send 5496 => sun4i, 10080 => sun6i bytes 190 | # of data at 0x40400000 191 | # => (SYS_PARA,...) 192 | # 20.FES_TRANSMIT: (:write, :dram): Send 172 bytes of data at 0x40410000 193 | # => "01 00 01 00 01 01 01 01 01 02 10 00 00 01 00 10..." @todo 194 | # 21.Write element: (update_boot0.axf, de), 114352 bytes at 0x40430000 195 | # 22.FES_RUN: Run code at 0x40430000, max_para => 17 (len) 196 | # => PARAMS = [0x40600000, 0x40400000, 0x40410000, 0x00000000] 197 | # 23.FES_INFO: => return 0x1,(0x00*) (32 bytes) until 0x100 => success? 198 | # 24.FES_GET_MSG: (address: 0x400) => probably returned data size 199 | # => returns 1024 bytes (...)"updateBootxOk000" 200 | # --------- End of update boot0 201 | # 25.FES_TRANSMIT: (:write, :dram): Send 4 bytes of data at 0x7E04 202 | # => 0x1234A5CD (finish flag) 203 | # 26. Write element (fet_restore.axf, de) 204 | # => Run params are 0x00s * 16 205 | # 206 | # --- Writing element 207 | # I.FES_TRANSMIT: (:write, :dram): Send 128 bytes of data at 208 | # 0x40330000 => sun4i, 0x40360000 => sun6i (magic_XX_start.fex) 209 | # II.FES_TRANSMIT: (:write, FLAG): Send X bytes of data at 0x40430000 (FILE) 210 | # III.FES_TRANSMIT: (:write, :dram): Send 128 bytes of data at 211 | # 0x40330000 => sun4i, 0x40360000 => sun6i (magic_XX_end.fex) 212 | # ---- End of writing element 213 | # 214 | # @example Flash process (A31s) (FES) (boot 2.0) 215 | # 1. FEL_VERIFY_DEVICE: Allwinner A31s (sun6i), revision 0, FW: 1, mode: fes 216 | # 2. FES_TRANSMIT (read flag, index:dram): Get 256 of data form 0x7e00 (filed 0xCC) 217 | # 3. FEL_VERIFY_DEVICE: Allwinner A31s (sun6i), revision 0, FW: 1, mode: fes 218 | # These 3 steps above seems optional 219 | # 4. FES_TRANSMIT: (write flag, index:dram): Send 256 of data at 0x7e00 (0x00000000, rest 0xCC) 220 | # 5. FES_DOWNLOAD: Send 16 bytes @ 0x0, flags erase|finish (0x17f04) ((DWORD)0x01, rest 0x00) 221 | # => Force sys_config.fex's erase_flag to 1 222 | # 6. FES_VERIFY_STATUS: flags erase (0x7f04). Return flags => 0x6a617603, crc => 0 223 | # 7. FES_DOWNLOAD: write sunxi_mbr.fex, whole file at once => 16384 * 4 copies bytes size 224 | # context: mbr|finish (0x17f01), inits NAND 225 | # 8. FES_VERIFY_STATUS: flags mbr (0x7f01). Return flags => 0x6a617603, crc => 0 226 | # *** Flashing process starts 227 | # 9. FES_FLASH_SET_ON: enable nand (actually it may intialize MMC I suppose), 228 | # not needed if we've done step 8 229 | # 10.FES_DOWNLOAD: write bootloader.fex (nanda) at 0x8000 in 65536 chunks, but address offset 230 | # must be divided by 512 => 65536/512 = 128. Thus (0x8000, 0x8080, 0x8100, etc) 231 | # at last chunk :finish context must be set 232 | # 11.FES_VERIFY_VALUE: I'm pretty sure args are address and data size @todo 233 | # Produces same as FES_VERIFY_STATUS => AWFESVerifyStatusResponse 234 | # and CRC must be the same value as stored in Vbootloader.fex 235 | # 12.FES_DOWNLOAD/FES_VERIFY_VALUE: write env.fex (nandb) at 0x10000 => because 236 | # previous partiton size was 0x8000 => see sys_partition.fex). 237 | # 13.FES_DOWNLOAD/FES_VERIFY_VALUE: write boot.fex (nandc) at 0x18000 238 | # 14.FES_DOWNLOAD/FES_VERIFY_VALUE: write system.fex (nandd) at 0x20000 239 | # 15.FES_DOWNLOAD/FES_VERIFY_VALUE: write recovery.fex (nandg) at 0x5B8000 240 | # 16.FES_FLASH_SET_OFF <= disable nand 241 | # 17.FES_DOWNLOAD: Send u-boot.fex at 0x00, context is uboot (0x7f02) 242 | # 18.FES_VERIFY_STATUS: flags uboot (0x7f04). Return flags => 0x6a617603, crc => 0 243 | # 19.FES_QUERY_STORAGE: => returns 0 [4 bytes] @todo 244 | # 20.FES_DOWNLOAD: Send boot0_nand.fex at 0x00, context is boot0 (0x7f03) 245 | # 21.FES_VERIFY_STATUS: flags boot0 (0x7f03). Return flags => 0x6a617603, crc => 0 246 | # 22.FES_SET_TOOL_MODE: Reboot device (8, 0) 247 | # *** Weee! We've finished! 248 | # @example Partition layout (can be easily recreated using sys_partition.fex or sunxi_mbr.fex) 249 | # => 1MB = 2048 in NAND addressing / 1 sector = 512 bytes 250 | # mbr (sunxi_mbr.fex) @ 0 [16MB] 251 | # bootloader (nanda) @ 0x8000 [16MB] 252 | # env (nandb) @ 0x10000 [16MB] 253 | # boot (nandc) @ 0x18000 [16MB] 254 | # system (nandd) @ 0x20000 [800MB] 255 | # data (nande) @ 0x1B0000 [2048MB] 256 | # misc (nandf) @ 0x5B0000 [16MB] 257 | # recovery (nandg) @ 0x5B8000 [32MB] 258 | # cache (nandh) @ 0x5C8000 [512MB] 259 | # databk (nandi) @ 0x6C8000 [128MB] 260 | # userdisk (nandj) @ 0x708000 [4096MB - 3584MB => 512MB for 4GB NAND] 261 | # Main class for program. Contains methods to communicate with the device 262 | class FELix 263 | 264 | # Open device, and setup endpoints 265 | # @param device [LIBUSB::Device] a device 266 | # @raise [FELFatal] 267 | def initialize(device = nil) 268 | if device 269 | raise FELFatal, "Unexcepted argument type: #{device.inspect}" unless device. 270 | kind_of?(LIBUSB::Device) 271 | @handle = device.open 272 | @handle.detach_kernel_driver(0) if RUBY_PLATFORM=~/linux/i && @handle.kernel_driver_active?(0) 273 | @handle.claim_interface(0) 274 | @usb_out = device.endpoints.select { |e| e.direction == :out }[0] 275 | @usb_in = device.endpoints.select { |e| e.direction == :in }[0] 276 | end 277 | rescue => e 278 | raise FELFatal, e.message 279 | end 280 | 281 | # Reconnect to currently connected device 282 | # @return true if reconnect succeed 283 | def reconnect? 284 | usb = LIBUSB::Context.new 285 | device = usb.devices(:idVendor => 0x1f3a, :idProduct => 0xefe8).select do |d| 286 | @handle.device.port_number == d.port_number 287 | end 288 | if device[0] 289 | # Close old handle 290 | @handle.release_interface(0) if @handle 291 | @handle.close if @handle 292 | # Open new one 293 | @handle = device[0].open 294 | @handle.detach_kernel_driver(0) if RUBY_PLATFORM=~/linux/i && @handle.kernel_driver_active?(0) 295 | @handle.claim_interface(0) 296 | @usb_out = device[0].endpoints.select { |e| e.direction == :out }[0] 297 | @usb_in = device[0].endpoints.select { |e| e.direction == :in }[0] 298 | true 299 | else 300 | @handle = nil 301 | false 302 | end 303 | end 304 | 305 | # Clean up on and finish program 306 | def bailout 307 | @handle.release_interface(0) if @handle 308 | @handle.close if @handle 309 | end 310 | 311 | end 312 | 313 | $exit_code = FELIX_SUCCESS 314 | $options = {} 315 | 316 | begin 317 | # ComputerInteger: hex strings (0x....) or decimal 318 | ComputerInteger = /(?:0x[\da-fA-F]+(?:_[\da-fA-F]+)*|\d+(?:_\d+)*)/ 319 | # MemoryInteger: extended ComputerInteger that accepts units (KB, MB, GB) 320 | MemoryInteger = /(?:0x[\dA-F]+(?:_[\dA-F]+)*|\d+(?:_\d+)*[kmg]?b?)/i 321 | Modes = [:fel, :fes] 322 | AddressCmds = [:write, :read, :run, :transmit, :crc] 323 | LengthCmds = [:read, :transmit, :crc] 324 | OptionParser.new do |opts| 325 | opts.banner = "Usage: felix action [options]" 326 | opts.separator "Actions:" 327 | 328 | opts.separator "* Common".light_blue.underline 329 | opts.on("--devices", "List the devices") do |v| 330 | devices = LIBUSB::Context.new.devices(:idVendor => 0x1f3a, 331 | :idProduct => 0xefe8) 332 | puts "No device found in FEL mode!" if devices.empty? 333 | devices.each_with_index do |d, i| 334 | puts "* %2d: (port %d) FEL device %d@%d %x:%x" % [i, d.port_number, 335 | d.bus_number, d.device_address, d.idVendor, d.idProduct] 336 | end 337 | exit FELIX_SUCCESS 338 | end 339 | opts.on("--version", "Show version") do 340 | puts FELIX_VERSION 341 | exit FELIX_SUCCESS 342 | end 343 | opts.separator "* Debugging".light_blue.underline 344 | opts.on("--decode-traffic path", String, "Decode packets from Wireshark" << 345 | " dump. Use with --verbose to dump full packet data") do |f| 346 | $options[:action] = :decode_packets 347 | $options[:file] = f 348 | $options[:dont_connect] = true 349 | end 350 | opts.on("--decode-mbr path", String, "Decode sunxi-mbr.fex") do |f| 351 | begin 352 | mbr = File.read(f, mode: "rb") 353 | AWMBR.read(mbr).inspect 354 | rescue Errno::ENOENT 355 | puts "File not found!" 356 | exit FELIX_FAIL 357 | end 358 | exit FELIX_SUCCESS 359 | end 360 | opts.on("--verify-sparse path", String, "Check if a sparse image is valid") do |f| 361 | begin 362 | header = File.read(f, 64, mode: "rb") # read first 64 bytes 363 | raise SparseError, "Not a sparse file" unless SparseImage.is_valid?(header) 364 | File.open(f, "rb") do |file| 365 | sparse = SparseImage.new(file) 366 | puts "File: #{f}" 367 | puts "Chunks: #{sparse.count_chunks}" 368 | 369 | raw = sparse.chunks.count {|ch| ch.chunk_type == ChunkType[:raw]} 370 | fill = sparse.chunks.count {|ch| ch.chunk_type == ChunkType[:fill]} 371 | dc = sparse.chunks.count {|ch| ch.chunk_type == ChunkType[:dont_care]} 372 | crc = sparse.chunks.count {|ch| ch.chunk_type == ChunkType[:crc32]} 373 | 374 | puts "* RAW: #{raw}\n* FILL: #{fill}\n* DONT_CARE: #{dc}\n" 375 | puts "* CRC #{crc}" if crc 376 | puts "Uncompressed size: #{sparse.get_final_size} (#{sparse. 377 | get_final_size >> 20} MB)" 378 | end 379 | rescue SparseError => e 380 | puts "Invalid image: ".red << "#{e}" 381 | exit FELIX_FAIL 382 | rescue Errno::ENOENT 383 | puts "File not found!" 384 | exit FELIX_FAIL 385 | end 386 | exit FELIX_SUCCESS 387 | end 388 | opts.on("--decompress-sparse path", String, "Decompress a sparse image (output to STDOUT!)") do |f| 389 | begin 390 | header = File.read(f, 64, mode: "rb") # read first 64 bytes 391 | raise SparseError, "Not a sparse file" unless SparseImage.is_valid?(header) 392 | File.open(f, "rb") do |file| 393 | sparse = SparseImage.new(file) 394 | STDERR.puts "File: #{f}" 395 | STDERR.puts "Uncompressed size: #{sparse.get_final_size} (#{sparse. 396 | get_final_size >> 20} MB)" 397 | STDERR.print "Decompressing..." 398 | i = 1 399 | sparse.each_chunk do |ch| 400 | print ch 401 | STDERR.print "\rDecompressing...%d/%d" % [i, sparse.count_chunks] 402 | i+= 1 403 | end 404 | STDERR.puts "\nDone" 405 | end 406 | rescue SparseError => e 407 | STDERR.puts "Invalid image: ".red << "#{e}" 408 | exit FELIX_FAIL 409 | rescue Errno::ENOENT 410 | STDERR.puts "File not found!" 411 | exit FELIX_FAIL 412 | end 413 | exit FELIX_SUCCESS 414 | end 415 | opts.on("--decode-dl path", String, "Decode dlinfo.fex") do |f| 416 | begin 417 | dl = File.read(f, mode: "rb") 418 | AWDownloadInfo.read(dl).inspect 419 | rescue Errno::ENOENT 420 | puts "File not found!" 421 | exit FELIX_FAIL 422 | end 423 | exit FELIX_SUCCESS 424 | end 425 | opts.separator "* Images".light_blue.underline 426 | opts.on("--image-info image", String, "Show LiveSuit's image info") do |f| 427 | begin 428 | FELHelpers.show_image_info(f) 429 | rescue Errno::ENOENT 430 | puts "File not found!" 431 | exit FELIX_FAIL 432 | end 433 | exit FELIX_SUCCESS 434 | end 435 | opts.on("--image-extract image", String, "Extract a LiveSuit " << 436 | "firmware image. Use with --item if you want to extract single item") do |f| 437 | $options[:action] = :extract 438 | $options[:file] = f 439 | $options[:dont_connect] = true 440 | end 441 | opts.on("--image-dram image", String, "Show DRAM config used in LiveSuit " << 442 | "firmware image") do |f| 443 | $options[:action] = :generate_dram 444 | $options[:file] = f 445 | $options[:dont_connect] = true 446 | end 447 | opts.on("--uboot-crc file", String, "Update CRC of a u-boot.fex file") do |f| 448 | begin 449 | uboot = UbootBinary.read(File.read(f, mode: "rb")) 450 | old_sum = uboot.header.check_sum.to_i 451 | uboot.header.check_sum = UBOOT_STAMP_VALUE 452 | new_sum = FELHelpers.checksum(uboot.to_binary_s) 453 | puts "* Current checksum: 0x%08x" % old_sum unless $options[:verbose] 454 | if new_sum != old_sum 455 | puts "* New checksum: 0x%08x" % new_sum unless $options[:verbose] 456 | print "* Writing new checksum".ljust(57) << "\t" unless $options[:verbose] 457 | File.open(f, "r+") do |file| 458 | file.seek(12, IO::SEEK_SET) 459 | val = BinData::Uint32le.new(new_sum) 460 | file.write(val.to_binary_s) 461 | end 462 | puts "[OK]".ljust(4).green unless $options[:verbose] 463 | else 464 | puts "* Checksum not changed" unless $options[:verbose] 465 | end 466 | rescue Errno::ENOENT 467 | puts "File not found!" 468 | exit FELIX_FAIL 469 | end 470 | exit FELIX_SUCCESS 471 | end 472 | opts.separator "* FEL/FES mode".light_blue.underline 473 | opts.on("--wfd", "Block until device is detected") do 474 | usb = LIBUSB::Context.new 475 | begin 476 | while usb.devices(:idVendor => 0x1f3a, :idProduct => 0xefe8).empty? do 477 | sleep 0.5 478 | end 479 | exit FELIX_SUCCESS 480 | rescue SignalException 481 | exit FELIX_FATAL 482 | end 483 | end 484 | opts.on("--flash image", String, "Flash Livesuit image") do |f| 485 | $options[:action] = :flash 486 | $options[:file] = f 487 | end 488 | opts.on("--tofes image", String, "Switch device into FES mode using" << 489 | " the given firmware image") do |f| 490 | $options[:action] = :boot2fes 491 | $options[:dest] = :usb_product 492 | $options[:file] = f 493 | end 494 | opts.on("--boot image", String, "Boot device using fes & u-boot from" << 495 | " the given firmware image") do |f| 496 | $options[:action] = :boot2fes 497 | $options[:dest] = :boot 498 | $options[:file] = f 499 | end 500 | opts.on("--status", "Get device status") { $options[:action] = :device_status } 501 | opts.on("--run", "Execute code. Use with --address") do 502 | $options[:action] = :run 503 | end 504 | opts.on("--read file", String, "Read memory to file. Use with --address" << 505 | ", --length, --context. In FES mode you can additionally specify" << 506 | " --tags, --item (other arguments are discarded then)") do |f| 507 | $options[:action] = :read 508 | $options[:file] = f 509 | end 510 | opts.on("--write file", String, "Write file to memory. Use with" << 511 | " --address, --context. In FES mode you can additionally specify " << 512 | "--tags, --item (other arguments are discarded then)") do |f| 513 | $options[:action] = :write 514 | $options[:file] = f 515 | end 516 | 517 | opts.separator "* Only in FES mode".light_blue.underline 518 | opts.on("--mbr mbr", "Write new MBR") do |f| 519 | $options[:action] = :mbr 520 | $options[:file] = f 521 | end 522 | opts.on("--mbr-dump", "Dump an MBR table from the device. NAND must" << 523 | " be enabled") do 524 | $options[:action] = :mbr_dump 525 | end 526 | opts.on("--nand how", [:on, :off], "Enable/disable NAND driver. Use 'on'" << 527 | " or 'off' as parameter)") do |b| 528 | $options[:action] = :storage 529 | $options[:how] = b 530 | end 531 | opts.on("--transmit file", "Read/write. May be used with --index" << 532 | ", --address, --length. Omit --length if you want to write. Default" << 533 | " index is :dram") do |f| 534 | $options[:action] = :transmit 535 | $options[:file] = f 536 | end 537 | opts.on("--crc", "Get checksum of the memory block. Must be used with " << 538 | "--address and --length") { $options[:action] = :crc } 539 | opts.on("--reboot", "Reboot device") { $options[:action] = :reboot } 540 | opts.on("--info", "Get code exection status") { $options[:action] = :info } 541 | opts.on("--status-secure", "Get security system status flag") do 542 | $options[:action] = :query_secure 543 | end 544 | opts.on("--status-storage", "Get storage type used to boot") do 545 | $options[:action] = :query_storage 546 | end 547 | 548 | opts.separator "\n* Options".light_blue.underline 549 | opts.on("-d", "--device num", Integer, 550 | "Select device number (default 0)") { |id| $options[:device] = id } 551 | 552 | opts.on("-a", "--address adr", ComputerInteger, "Address (used for" << 553 | " --" << AddressCmds.join(", --") << ")") do |a| 554 | $options[:address] = a[0..1] == "0x" ? Integer(a, 16) : a.to_i 555 | end 556 | opts.on("-l", "--length len", MemoryInteger, "Length of data (used " << 557 | "for --" << LengthCmds.join(", --") << "). You can use KB, MB, GB " << 558 | "units.") do |l| 559 | multipier = case l[/.*\d+(.*)$/, 1].upcase 560 | when "KB", "K" then 1024 561 | when "MB", "M" then 1024*1024 562 | when "GB", "G" then 1024*1024*1024 563 | else 1 564 | end 565 | $options[:length] = (l[0..1] == "0x" ? Integer(l, 16) : l.to_i) * multipier 566 | end 567 | opts.on("-c", "--context ctx", Modes, "Set command context to one of " << 568 | "mode (" << Modes.join(", ") << "). Default: fel") do |m| 569 | $options[:mode] = m.to_sym 570 | end 571 | opts.on("--index idx", FESIndex.keys, "Set media index " << 572 | "(" << FESIndex.keys.join(", ") << ")") do |i| 573 | $options[:index] = i.to_sym 574 | end 575 | opts.on("-t", "--tags t,a,g", Array, "One or more tag (" << 576 | AWTags.keys.join(", ") << ")") do |t| 577 | $options[:tags] = t.map(&:to_sym) # Convert every value to symbol 578 | end 579 | opts.on("-i", "--item name", String, "File from image to extract (without" << 580 | " the path) or partition name (e.g. boot, system)") do |it| 581 | $options[:item] = it 582 | end 583 | opts.on_tail("-v", "--verbose", "Verbose traffic") do 584 | $options[:verbose] = true 585 | end 586 | opts.on_tail("-f", "--force", "Force storage format (for --flash and --mbr)") do 587 | $options[:format] = true 588 | end 589 | opts.on_tail("-s", "--silent", "Don't print a banner") do 590 | $options[:silent] = true 591 | end 592 | opts.on_tail("--decompress", "Decompress sparse image (for --image-extract)") do 593 | $options[:decompress] = true 594 | end 595 | end.parse! 596 | $options[:tags] = [:none] unless $options[:tags] 597 | $options[:mode] = :fel unless $options[:mode] 598 | # if argument is specfied we want to receive data from the device 599 | $options[:direction] = [:read, :transmit].include?($options[:action]) ? :read : :write 600 | 601 | raise OptionParser::InvalidArgument, "Invalid tag. Please specify one or" << 602 | " more of " << AWTags.keys.join(", ") unless ($options[:tags] - AWTags. 603 | keys).empty? 604 | raise OptionParser::MissingArgument, "You must specify --address and " << 605 | "--length" if($options[:action] == :crc && ($options[:length] == nil || 606 | $options[:address] == nil)) 607 | raise OptionParser::MissingArgument, "You must specify --address and " << 608 | "--length or --item!" if($options[:direction] == :read && !$options[:item] && 609 | ($options[:length] == nil || $options[:address] == nil) && [:read, :transmit]. 610 | include?($options[:action])) 611 | raise OptionParser::MissingArgument, "You must specify --address!" if 612 | $options[:direction] == :write && !$options[:item] && !$options[:address] && 613 | [:write, :transmit, :run].include?($options[:action]) 614 | rescue OptionParser::MissingArgument => e 615 | puts "#{e.message.camelize}. Type felix --help to see usage" 616 | exit FELIX_FAIL 617 | rescue OptionParser::InvalidArgument => e 618 | puts "#{e.message.camelize}. Type felix --help to see usage" 619 | exit FELIX_FAIL 620 | rescue OptionParser::InvalidOption 621 | puts "Unknown option. Type felix --help to see usage" 622 | exit FELIX_FAIL 623 | end unless $FELIX_TEST_CASE 624 | 625 | begin 626 | unless $options[:silent] 627 | version = "FEL".red << "ix " << FELIX_VERSION << " by Lolet" 628 | puts version 629 | puts "Warning:".red << " I don't give any warranty on this software" << 630 | ". You use it at own risk!" 631 | puts "─" * version.length 632 | end 633 | 634 | unless $options[:dont_connect] 635 | usb = LIBUSB::Context.new 636 | devices = usb.devices(:idVendor => 0x1f3a, :idProduct => 0xefe8) 637 | raise FELFatal, "No device found in FEL mode!" if devices.empty? 638 | # If there's more than one device list and ask to select 639 | raise FELFatal, "Found more than 1 device (use --device parameter)" if 640 | devices.size > 1 && $options[:device] == nil 641 | 642 | $options[:device] ||= 0 643 | 644 | dev = devices[$options[:device]] 645 | print "* Connecting to device at port %d, FEL device %d@%d %x:%x\t" % [ 646 | dev.port_number, dev.bus_number, dev.device_address, dev.idVendor, 647 | dev.idProduct] 648 | end 649 | fel = [:flash, :extract, :boot2fes, :generate_dram].include?($options[:action] 650 | ) ? FELSuit.new(dev, $options[:file]) : FELix.new(dev) 651 | puts "[OK]".green if dev 652 | 653 | case $options[:action] 654 | when :crc 655 | print ("* Getting CRC for 0x%08x (%d bytes)" % [$options[:address], $options[:length]]).ljust(57) << 656 | "\t" unless $options[:verbose] 657 | status = fel.verify_value($options[:address], $options[:length]) 658 | puts "[OK]".green unless $options[:verbose] 659 | status.pp 660 | when :device_status 661 | print fel.get_device_status.inspect 662 | when :info 663 | print "* Getting info".ljust(57) << "\t" unless $options[:verbose] 664 | status = fel.info 665 | puts "[OK]".green unless $options[:verbose] 666 | AWFESInfoResponse.read(status).pp 667 | when :query_secure 668 | print "* Checking security flag".ljust(57) << "\t" unless $options[:verbose] 669 | status = fel.query_secure 670 | puts "[OK]".green unless $options[:verbose] 671 | puts "* Security flag is: %s (%d)" % [ status.to_s.yellow, AWSecureStatusMode[status] ] 672 | when :query_storage 673 | print "* Checking storage flag".ljust(57) << "\t" unless $options[:verbose] 674 | status = fel.query_storage 675 | puts "[OK]".green unless $options[:verbose] 676 | puts "* Used storage is: %s (%d)" % [status.to_s.yellow, AWStorageType[status]] 677 | when :mbr 678 | print ("* Writing new paratition table" << ($options[:format] ? " and formatting storage" : 679 | "")).ljust(57) << "\t" unless $options[:verbose] 680 | mbr = File.read($options[:file], mode: "rb") 681 | status = fel.write_mbr(mbr, $options[:format]) 682 | raise FELError, "Write failed (#{status.crc})" if status.crc != 0 683 | puts "[OK]".green unless $options[:verbose] 684 | when :mbr_dump 685 | print "* #{$options[:mode]}: Reading MBR".ljust(57) << "\t" unless $options[:verbose] 686 | data = fel.read(0, 16384, :none, :fes) do |b| 687 | print "\r* Reading MBR".ljust(57) << "\t" unless $options[:verbose] 688 | percent = (b * 100) / 16384 689 | print "#{percent}%".ljust(4).light_blue unless percent == 100 690 | end 691 | begin 692 | mbr = AWMBR.read(data) 693 | rescue BinData::ValidityError => e 694 | raise FELFatal, "\nInvalid MBR. Please use --mbr to write the partition table" 695 | end 696 | puts "[OK]".green unless $options[:verbose] 697 | mbr.inspect 698 | when :storage 699 | print "* Setting flash state to #{$options[:how]}".ljust(57) << "\t" unless 700 | $options[:verbose] 701 | fel.set_storage_state($options[:how]) 702 | puts "[OK]".green unless $options[:verbose] 703 | when :read 704 | if $options[:item] 705 | # Read all data from MBR 706 | print "* #{$options[:mode]}: Reading MBR".ljust(57) << "\t" unless $options[:verbose] 707 | data = fel.read(0, 16384, :none, :fes) do |b| 708 | print "\r* Reading MBR".ljust(57) << "\t" unless $options[:verbose] 709 | percent = (b * 100) / 16384 710 | print "#{percent}%".ljust(4).light_blue unless percent == 100 711 | end 712 | begin 713 | mbr = AWMBR.read(data) 714 | rescue BinData::ValidityError => e 715 | raise FELFatal, "\nInvalid MBR. Please use --mbr to write the partition table" 716 | end 717 | part = mbr.part_by_name($options[:item]) 718 | raise FELError, "Partition not found in boot record" unless part 719 | puts "[OK]".green unless $options[:verbose] 720 | $options[:address] = part.address_low 721 | $options[:length] = part.lenlo * FELIX_SECTOR 722 | $options[:mode] = :fes 723 | end 724 | f = File.open($options[:file], "wb") 725 | print "* #{$options[:mode]}: Reading data".ljust(57) << "\t" unless $options[:verbose] 726 | data = fel.read($options[:address], $options[:length], $options[:tags], 727 | $options[:mode]) do |b, chunk| 728 | print ("\r* #{$options[:mode]}: Reading data (" << 729 | "#{b}/#{$options[:length]} bytes)").ljust(57) << "\t" unless $options[:verbose] 730 | percent = (b * 100) / $options[:length] 731 | f.write(chunk) 732 | print "#{percent}%".ljust(4).light_blue unless percent == 100 733 | end 734 | puts "[OK]".green unless $options[:verbose] 735 | when :write 736 | header = File.read($options[:file], 64, mode: "rb") # read first 64 bytes 737 | file_size = 0 738 | sparsed = SparseImage.is_valid?(header) 739 | if sparsed 740 | File.open($options[:file], "rb") do |file| 741 | sparse = SparseImage.new(file) 742 | file_size = sparse.get_final_size 743 | end 744 | else 745 | file_size = File.size($options[:file]) 746 | end 747 | if $options[:item] 748 | # Read all data from MBR 749 | $options[:mode] = :fes 750 | print "* #{$options[:mode]}: Reading MBR".ljust(57) << "\t" unless $options[:verbose] 751 | begin 752 | data = fel.read(0, 16384, :none, $options[:mode]) do |b| 753 | print "\r* #{$options[:mode]}: Reading MBR".ljust(57) << "\t" unless $options[:verbose] 754 | percent = (b * 100) / 16384 755 | print "#{percent}%".ljust(4).light_blue unless percent == 100 756 | end 757 | rescue FELFatal => e 758 | raise FELFatal, "\nFailed to get device MBR. Did you enable NAND access? (--nand)" 759 | end 760 | begin 761 | mbr = AWMBR.read(data) 762 | rescue BinData::ValidityError => e 763 | raise FELFatal, "\nInvalid MBR. Please use --mbr to write the partition table" 764 | end 765 | part = mbr.part_by_name($options[:item]) 766 | raise FELError, "Partition not found in boot record" unless part 767 | puts "[OK]".green unless $options[:verbose] 768 | $options[:address] = part.address_low 769 | real_len = part.lenlo * FELIX_SECTOR 770 | raise FELError, "File is too big (partition size is " << 771 | "#{real_len})" if file_size > real_len 772 | end 773 | print ("* Reading a file (#{file_size} bytes)") 774 | curr_add = $options[:address] 775 | if sparsed 776 | File.open($options[:file], "rb") do |file| 777 | sparse = SparseImage.new(file) 778 | queue = Queue.new 779 | len = 0 780 | threads = [] 781 | threads << Thread.new do 782 | i = 1 783 | sparse.each_chunk do |ch, type| 784 | len += ch.length 785 | print ("\r* Decompressing...").ljust(57) << "\t" unless $options[:verbose] 786 | queue << [ch, type] 787 | i+=1 788 | # Optimize memory usage (try to keep heap at 128MB) 789 | while len > (128 << 20) && !queue.empty? 790 | sleep 0.5 791 | end 792 | end 793 | end 794 | threads << Thread.new do 795 | written = 0 796 | sparse.count_chunks.times do |chunk_num| 797 | data, chunk_type = queue.pop 798 | len -= data.length 799 | written+=data.bytesize 800 | 801 | finish = (chunk_num == sparse.count_chunks - 1) || sparse.chunks[ 802 | chunk_num + 1].chunk_type == ChunkType[:dont_care] && (chunk_num == 803 | sparse.count_chunks - 2) 804 | fel.write(curr_add, data, $options[:tags], $options[:mode], !finish) do |ch| 805 | print ("\r* #{$options[:mode]}: Writing data (" << 806 | "#{written}/#{file_size} bytes)").ljust(57) << "\t" unless $options[:verbose] 807 | percent = (written * 100) / file_size 808 | print "#{percent}%".ljust(4).light_blue unless percent == 100 809 | end 810 | break if finish 811 | next_sector=data.bytesize / FELIX_SECTOR 812 | curr_add+=( next_sector ? next_sector : 1) # Write next sector if its less than FELIX_SECTOR 813 | end 814 | end 815 | threads.each {|t| t.join} 816 | end 817 | else 818 | queue = Queue.new 819 | len = 0 820 | threads = [] 821 | # reader 822 | threads << Thread.new do 823 | File.open($options[:file], "rb") do |file| 824 | while not file.eof? 825 | chunk = file.read(FELIX_MAX_CHUNK) 826 | len+=chunk.bytesize 827 | print ("\r* Reading a file").ljust(57) << "\t" unless $options[:verbose] 828 | queue << chunk 829 | # Optimize memory usage (try to keep heap at 128MB) 830 | while len > (128 << 20) && !queue.empty? 831 | sleep 0.5 832 | end 833 | end 834 | end 835 | end 836 | # writter 837 | threads << Thread.new do 838 | written = 0 839 | while written < file_size 840 | data = queue.pop 841 | written+=data.bytesize 842 | len-=data.bytesize 843 | fel.write(curr_add, data, $options[:tags], $options[:mode], written < file_size) do |b| 844 | print ("\r* #{$options[:mode]}: Writing data (" << 845 | "#{written}/#{file_size} bytes)").ljust(57) << "\t" unless $options[:verbose] 846 | percent = (written * 100) / file_size 847 | print "#{percent}%".ljust(4).light_blue unless percent == 100 848 | end 849 | tags = 0 850 | if $options[:tags].kind_of?(Array) 851 | $options[:tags].each {|t| tags |= AWTags[t]} 852 | else 853 | tags |= AWTags[$options[:tags]] 854 | end 855 | 856 | if tags & AWTags[:dram] == 0 && $options[:mode] == :fes 857 | next_sector=data.bytesize / FELIX_SECTOR 858 | curr_add+=( next_sector ? next_sector : 1) # Write next sector if its less than FELIX_SECTOR 859 | else 860 | curr_add+=data.bytesize 861 | end 862 | end 863 | end 864 | threads.each {|t| t.join} 865 | end 866 | puts "[OK]".green unless $options[:verbose] 867 | 868 | unless $options[:mode] == :fel || sparsed 869 | print ("\r* Verifying").ljust(57) << "\t" unless $options[:verbose] 870 | resp = fel.verify_status($options[:tags]) 871 | raise FELFatal, "Verification failed" if resp.crc != 0 872 | puts "[OK]".green unless $options[:verbose] 873 | end 874 | when :run 875 | print ("* #{$options[:mode]}: Executing code @ 0x%08x" % 876 | $options[:address]).ljust(57) << "\t" unless $options[:verbose] 877 | fel.run($options[:address], $options[:mode]) 878 | puts "[OK]".green unless $options[:verbose] 879 | when :transmit 880 | if $options[:direction] == :write 881 | print "* Reading file" unless $options[:verbose] 882 | data = File.read($options[:file], mode: "rb") 883 | print " (#{data.bytesize} bytes)".ljust(57) << "\t" unless $options[:verbose] 884 | end 885 | data ||= nil 886 | data = fel.transmit($options[:direction], :address => $options[:address], 887 | :length => $options[:length], :memory => data, :media_index => 888 | $options[:index]) do |b| 889 | print ("\r* #{$options[:mode]}: Transmiting data (" << 890 | "#{b}/#{$options[:length]} bytes)").ljust(57) << "\t" unless $options[:verbose] 891 | percent = (b * 100) / $options[:length] 892 | print "#{percent}%".ljust(4).light_blue unless percent == 100 893 | end 894 | if $options[:direction] == :read && data 895 | print "\r* Writing data (#{data.length} bytes )".ljust(57) << "\t" 896 | File.open($options[:file], "wb") { |f| f.write(data) } 897 | end 898 | puts "[OK]".green unless $options[:verbose] 899 | when :reboot 900 | print "* Rebooting".ljust(57) << "\t" unless $options[:verbose] 901 | fel.set_tool_mode(:usb_tool_update, :none) 902 | puts "[OK]".green unless $options[:verbose] 903 | when :extract 904 | if $options[:item] 905 | # Extract single item 906 | fn = "#{$options[:file]}_#{$options[:item]}" 907 | fn = "#{Dir.pwd}/#{File.basename(fn)}" 908 | print "* Extracting to #{fn}".ljust(57) << "\t" 909 | item = fel.structure.item_by_file($options[:item]) 910 | raise FELError, "Item not found!" unless item 911 | File.open(fn, "wb") do |f| 912 | if $options[:decompress] && SparseImage.is_valid?(fel.get_image_data( 913 | item, 64, 64)) 914 | sys_handle = fel.get_image_handle(item) 915 | sparse = SparseImage.new(sys_handle, item.off_len_low) 916 | sparse.each_chunk { |ch| f.write(ch) } 917 | sys_handle.close 918 | else 919 | p item 920 | fel.get_image_data(item, 8192, item.file_len_low) { |d| f.write(d) } 921 | end 922 | end 923 | puts "[OK]".green 924 | else 925 | # Extract everything we find 926 | dir = "#{Dir.pwd}/#{File.basename($options[:file])}_unpacked" 927 | puts "* Extracting to #{dir}" 928 | unless File.exists?(dir) 929 | print "* Creating #{dir}".ljust(57) << "\t" 930 | Dir.mkdir(dir) 931 | puts "[OK]".green 932 | end 933 | fel.structure.item.each do |it| 934 | path = it.path 935 | # Trim silly addition from imgRePacker 936 | path = path[12..-1] if path.start_with?("imgRePacker") 937 | file = File.basename(path) 938 | path = "#{dir}/#{File.dirname(path)}" 939 | print "* #{file}".ljust(57) << "\t" 940 | FileUtils.mkdir_p(path) unless File.exists?(path) 941 | File.open("#{path}/#{file}", "w") do |f| 942 | written = 0 943 | if $options[:decompress] && SparseImage.is_valid?(fel.get_image_data( 944 | it, 64, 64)) 945 | sys_handle = fel.get_image_handle(it) 946 | sparse = SparseImage.new(sys_handle, it.off_len_low) 947 | sparse.each_chunk do |ch| 948 | f.write(ch) 949 | written+=ch.bytesize 950 | percent = written * 100 / sparse.get_final_size 951 | print "\r* #{file}".ljust(57) << "\t" 952 | print "#{percent}%".ljust(4).light_blue if percent < 100 953 | end 954 | sys_handle.close 955 | else 956 | fel.get_image_data(it, 8192, it.file_len_low) do |d| 957 | f.write(d) 958 | written+=d.bytesize 959 | percent = written * 100 / it.file_len_low 960 | print "\r* #{file}".ljust(57) << "\t" 961 | print "#{percent}%".ljust(4).light_blue if percent < 100 962 | end 963 | end 964 | end 965 | puts "[OK]".green 966 | end 967 | end 968 | when :generate_dram 969 | print "* Generating DRAM".ljust(57) << "\t" 970 | cfg = "" 971 | leg = false 972 | if fel.structure.item_by_file("sys_config1.fex") 973 | cfg = fel.get_image_data(fel.structure.item_by_file("sys_config1.fex")) 974 | cfg << fel.get_image_data(fel.structure.item_by_file("sys_config.fex")) 975 | leg = true 976 | else 977 | cfg = fel.get_image_data(fel.structure.item_by_file("sys_config.fex")) 978 | end 979 | dram = FELHelpers.create_dram_config(cfg, leg) 980 | puts "[OK]".ljust(4).green 981 | dram.pp 982 | when :decode_packets 983 | FELHelpers.debug_packets($options[:file]) 984 | when :flash 985 | print "* Checking device state".ljust(57) << "\t" unless $options[:verbose] 986 | last_type = nil 987 | fel.flash($options[:format]) do |s, type, arg| 988 | unless $options[:verbose] 989 | if type == :percent && arg 990 | str = "" 991 | str << "\r* #{s}".ljust(57) << "\t" 992 | str << "#{arg}%".ljust(4).light_blue if arg < 100 993 | print str 994 | elsif type == :info 995 | puts "\n #{s}".white 996 | elsif type == :warn 997 | puts "[#{s}]".ljust(4).yellow 998 | else 999 | puts "[OK]".ljust(4).green unless last_type && last_type != :percent 1000 | print "* #{s}".ljust(57) << "\t" 1001 | end 1002 | last_type = type 1003 | end 1004 | end 1005 | when :boot2fes 1006 | print "* Checking device info".ljust(57) << "\t" unless $options[:verbose] 1007 | info = fel.get_device_status 1008 | raise FELError, "Device already in FES mode" if info.mode == AWDeviceMode[:fes] 1009 | puts "[OK]".ljust(4).green unless $options[:verbose] 1010 | print "* Setting work mode to: #{$options[:dest]}".ljust(57) << "\t" unless $options[:verbose] 1011 | item_uboot = fel.structure.item_by_file("u-boot.fex") 1012 | item_fes = fel.structure.item_by_file("fes1.fex") 1013 | if item_uboot && item_fes 1014 | uboot = fel.get_image_data(item_uboot) 1015 | fes = fel.get_image_data(item_fes) 1016 | fel.boot_to_fes(fes, uboot, $options[:dest]) 1017 | else 1018 | item_fes11 = fel.structure.item_by_file("fes_1-1.fex") 1019 | item_fes12 = fel.structure.item_by_file("fes_1-2.fex") 1020 | item_fes = fel.structure.item_by_file("fes.fex") 1021 | item_fes2 = fel.structure.item_by_file("fes_2.fex") 1022 | raise FELError, "Failed to get required items from image" unless ( 1023 | item_fes11 && item_fes12 && item_fes && item_fes2) 1024 | raise FELFatal, "Booting to other mode is not implemented for" << 1025 | " legacy images yet" unless $options[:dest] == :usb_product 1026 | fes11 = fel.get_image_data(item_fes11) 1027 | fes12 = fel.get_image_data(item_fes12) 1028 | fes = fel.get_image_data(item_fes) 1029 | fes2 = fel.get_image_data(item_fes2) 1030 | fel.boot_to_fes_legacy(fes11, fes12, fes, fes2) 1031 | end 1032 | puts "[OK]".ljust(4).green unless $options[:verbose] 1033 | else 1034 | raise FELFatal, "No action specified" 1035 | end 1036 | 1037 | rescue FELError => e 1038 | puts "[FAIL]".red unless $options[:verbose] 1039 | puts "#{e.message}" 1040 | $exit_code = FELIX_FAIL 1041 | rescue FELFatal => e 1042 | puts "[FATAL]\t".red << "#{e.message}" 1043 | $exit_code = FELIX_FATAL 1044 | rescue LIBUSB::ERROR_NOT_SUPPORTED 1045 | puts "[FATAL]\t".red << "You must install a libusb filter on your usb device driver" 1046 | $exit_code = FELIX_FAIL 1047 | rescue SignalException 1048 | puts "\n[HALT]\t".red << "Action aborted" 1049 | $exit_code = FELIX_FATAL 1050 | rescue => e 1051 | puts "[WTF]".red 1052 | puts "#{e.class}: #{e.message} at #{e.backtrace.join("\n")}" 1053 | $exit_code = FELIX_FATAL 1054 | ensure 1055 | begin 1056 | print "* Cleaning up".ljust(57) << "\t" 1057 | # Cleanup the handle 1058 | fel.bailout if fel 1059 | puts "[OK]".green 1060 | exit $exit_code 1061 | rescue => e 1062 | puts "[FATAL]".red 1063 | puts "Error: #{e.message} at #{e.backtrace.join("\n")}" 1064 | exit FELIX_FAIL 1065 | end 1066 | end unless $FELIX_TEST_CASE 1067 | -------------------------------------------------------------------------------- /libsparse.rb: -------------------------------------------------------------------------------- 1 | # libsparse.rb 2 | # Copyright 2014-2015 Bartosz Jankowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'bindata' 18 | 19 | # Sparse file exception filter class 20 | class SparseError < StandardError 21 | end 22 | 23 | #Sparse image chunk types 24 | ChunkType = { 25 | :raw => 0xcac1, 26 | :fill => 0xcac2, 27 | :dont_care => 0xcac3, 28 | :crc32 => 0xcac4 29 | } 30 | 31 | # Android sparse ext4 image header 32 | # As stated in (https://github.com/android/platform_system_core/blob/master/libsparse/sparse_format.h) 33 | class SparseImageHeader < BinData::Record 34 | endian :little 35 | uint32 :magic, :asserted_value => 0xed26ff3a 36 | # major_version (0x1) - reject images with higher major versions 37 | uint16 :major_version, :assert => lambda { value <= 1 } 38 | uint16 :minor_version # (0x0) - allow images with higer minor versions 39 | # 28 bytes for first revision of the file format 40 | uint16 :file_hdr_sz, :asserted_value => 28 41 | # 12 bytes for first revision of the file format 42 | uint16 :chunk_hdr_sz, :asserted_value => 12 43 | # block size in bytes, must be a multiple of 4 (4096) 44 | uint32 :blk_sz, :assert => lambda { value % 4 == 0} 45 | uint32 :total_blks # total blocks in the non-sparse output image 46 | uint32 :total_chunks # total chunks in the sparse input image 47 | uint32 :image_checksum # CRC32 checksum of the original data, counting "don't care" 48 | # as 0. Standard 802.3 polynomial, use a Public Domain 49 | # table implementation 50 | end 51 | 52 | # Android sparse ext4 image chunk 53 | class SparseImageChunk < BinData::Record 54 | endian :little 55 | # 0xCAC1 -> :raw 0xCAC2 -> :fill 0xCAC3 -> :dont_care 56 | uint16 :chunk_type, :assert => lambda { ChunkType.has_value? value } 57 | uint16 :reserved 58 | uint32 :chunk_sz # in blocks in output image 59 | uint32 :total_sz # in bytes of chunk input file including chunk header and data 60 | end 61 | 62 | # Main class 63 | class SparseImage 64 | 65 | attr_reader :chunks 66 | 67 | # Check sparse image validity 68 | # @param data [IO] image handle 69 | # @param offset [Integer] file offset 70 | # @raise [SparseError] if fail 71 | def initialize(data, offset = 0) 72 | @file = data 73 | if !(@file.class <= IO) && !@file.instance_of?(StringIO) 74 | raise SparseError, "Argument must be (sub)class of IO" 75 | end 76 | @chunks = [] 77 | @offset = offset 78 | @file.seek(@offset, IO::SEEK_SET) 79 | @header = SparseImageHeader.read(@file) 80 | @header.total_chunks.times do 81 | chunk = SparseImageChunk.read(@file) 82 | @chunks << chunk 83 | @file.seek(chunk.total_sz - @header.chunk_hdr_sz, IO::SEEK_CUR) 84 | end 85 | rescue BinData::ValidityError => e 86 | raise SparseError, "Not a sparse file (#{e})" 87 | rescue => e 88 | raise SparseError, "Initialize error (#{e})" 89 | end 90 | 91 | # Check if given data is a sparse file (it checks only validity of header 92 | # contrary to #initialize) 93 | # @param data [String] binary data 94 | # @return [TrueClass, FalseClass] true if header is valid 95 | def self.is_valid?(data) 96 | return false if data.length < 32 # stop if data is less than header size 97 | @header = SparseImageHeader.read(data) 98 | true 99 | rescue BinData::ValidityError 100 | false 101 | end 102 | 103 | # Dump decompressed image 104 | # @param filename [String] output image path 105 | def dump(filename) 106 | out = File.open(filename, "wb") 107 | each_chunk do |chunk| 108 | out << chunk 109 | end 110 | ensure 111 | out.close 112 | end 113 | 114 | # Read chunks and yield data 115 | # @yieldparam [String] binary data of chunk 116 | # @yieldparam [Symbol] type of chunk 117 | def each_chunk 118 | @file.seek(@offset, IO::SEEK_SET) 119 | @file.seek(@header.file_hdr_sz, IO::SEEK_CUR) 120 | @chunks.each do |c| 121 | @file.seek(@header.chunk_hdr_sz, IO::SEEK_CUR) 122 | data = "" 123 | case c.chunk_type 124 | when ChunkType[:raw] 125 | data << @file.read(c.total_sz - @header.chunk_hdr_sz) 126 | when ChunkType[:fill] 127 | num = @file.read(4) 128 | data << num * ((@header.blk_sz / 4) * c.chunk_sz) 129 | when ChunkType[:crc32] 130 | num = @file.read(4) 131 | when ChunkType[:dont_care] 132 | data << "\0" * (c.chunk_sz * @header.blk_sz) 133 | end 134 | yield data, ChunkType.invert[c.chunk_type] 135 | end 136 | end 137 | 138 | def [](i) 139 | raise SparseError, "Chunk not found" unless @chunks[i] 140 | @file.seek(@offset, IO::SEEK_SET) 141 | @file.seek(@header.file_hdr_sz, IO::SEEK_CUR) 142 | toseek = 0 143 | data = "" 144 | @chunks.each_with_index do |c, idx| 145 | skip = false 146 | if i > idx 147 | toseek += c.total_sz 148 | next 149 | end 150 | @file.seek(toseek + @header.chunk_hdr_sz, IO::SEEK_CUR) 151 | case c.chunk_type 152 | when ChunkType[:raw] 153 | data << @file.read(c.total_sz - @header.chunk_hdr_sz) 154 | when ChunkType[:fill] 155 | num = @file.read(4) 156 | data << num * ((@header.blk_sz / 4) * c.chunk_sz) 157 | when ChunkType[:crc32] 158 | num = @file.read(4) 159 | when ChunkType[:dont_care] 160 | data << "\0" * (c.chunk_sz * @header.blk_sz) 161 | end 162 | return data 163 | end 164 | end 165 | 166 | # Get size of unsparsed image (bytes) 167 | def get_final_size 168 | @header.total_blks * @header.blk_sz 169 | end 170 | 171 | # Get number of chunks 172 | def count_chunks 173 | @header.total_chunks 174 | end 175 | 176 | end 177 | -------------------------------------------------------------------------------- /spec/assets/bootloader.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankowskib/FELix/3101ba0b1f04f8e359d95d5b11db8950e2883af6/spec/assets/bootloader.sample -------------------------------------------------------------------------------- /spec/assets/env.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankowskib/FELix/3101ba0b1f04f8e359d95d5b11db8950e2883af6/spec/assets/env.sample -------------------------------------------------------------------------------- /spec/assets/sparse1.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankowskib/FELix/3101ba0b1f04f8e359d95d5b11db8950e2883af6/spec/assets/sparse1.sample -------------------------------------------------------------------------------- /spec/assets/sys_config.sample: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------------------------------------------- 2 | ; ˵Ă÷Łş ˝Ĺ±ľÖеÄ×Ö·ű´®Çř·Ö´óĐˇĐ´Ł¬ÓĂ»§żÉŇÔĐ޸Ä"="şóĂćµÄĘýֵ٬µ«ĘDz»ŇŞĐ޸ÄǰĂćµÄ×Ö·ű´® 3 | ;--------------------------------------------------------------------------------------------------------- 4 | 5 | [platform] 6 | chip=0x02000000 7 | pid =0x02000000 8 | sid =0x02000100 9 | bid =0x80 10 | 11 | eraseflag = 1 12 | jtag = 1 13 | 14 | [fex_misc] 15 | restore = 0 16 | 17 | ;------------------------------------------------------------------------------------------ 18 | ; ϵͳĹäÖĂ 19 | ;------------------------------------------------------------------------------------------ 20 | 21 | 22 | 23 | 24 | ;-------------------------------------------------------------------------------------------------- 25 | ; ąĚĽţĎÂÔزÎĘýĹäÖĂ 26 | ;-------------------------------------------------------------------------------------------------- 27 | ;----------------------------------------------------------------------------------------- 28 | ; ·ÖÇřĹäÖĂąÜŔí 29 | ;----------------------------------------------------------------------------------------- 30 | ;**************************************************** 31 | ; mbrµÄ´óС, ŇÔKbyteÎŞµĄÎ» 32 | ;**************************************************** 33 | [mbr] 34 | size = 16384 35 | 36 | ;******************************************************************************************************** 37 | ; ·ÖÇřĹäÖĂ 38 | ; 39 | ; [part_num] 40 | ; num = 2 ; //ÓĐ2¸ö·ÖÇř 41 | ; part0 = partiton0 ; //part0µÄĐĹϢ´ć·ĹÔÚpartiton0Ŕď 42 | ; parti = partitoni ; //partiµÄĐĹϢ´ć·ĹÔÚpartitoniŔď 43 | ; 44 | ; partition ¶¨Ňĺ·¶Ŕý: 45 | ; [partition2] ; //µÚ2¸ö·ÖÇř 46 | ; class_name = DISK ; //É豸ŔŕĂű, Č磺"DISK"ˇŁ 47 | ; name = USERFS2 ; //É豸˝ÚµăĂű. 48 | ; size_hi = 0 ; //·ÖÇř´óСµÄ¸ß32λ, µĄÎ»: Kbyte. 49 | ; size_lo = 32 ; //·ÖÇř´óСµÄµÍ32λ, µĄÎ»: Kbyte. 50 | ; 51 | ; ×˘Łş1ˇ˘nameΨһ, ˛»ÔĘĐíͬĂű 52 | ; 2ˇ˘class_nameşÍname×î´ó12¸ö×Ö·ű 53 | ; 3ˇ˘size_hi + size_lo = 0, ±íĘľ´Ë·ÖÇř˛»´ćÔÚ 54 | ; 4ˇ˘ÎŞÁ˰˛Č«şÍЧÂĘżĽÂÇŁ¬·ÖÇř´óС×îşĂ±ŁÖ¤ÎŞ16M×Ö˝ÚµÄŐűĘý±¶ 55 | ;******************************************************************************************************** 56 | [part_num] 57 | num = 9 58 | 59 | ;------------------------------>nanda, known as bootfs before, includes boot.axf u-boot.bin etc... 60 | [partition0] 61 | class_name = DISK 62 | name = bootloader 63 | size_hi = 0 64 | size_lo = 16384 65 | user_type = 0 66 | ro = 0 67 | 68 | ;------------------------------>nandb, enviroment for u-boot 69 | [partition1] 70 | class_name = DISK 71 | name = env 72 | size_hi = 0 73 | size_lo = 16384 74 | user_type = 0 75 | ro = 0 76 | 77 | ;------------------------------>nandc, kernel and ramdisk 78 | [partition2] 79 | class_name = DISK 80 | name = boot 81 | size_hi = 0 82 | size_lo = 32768 83 | user_type = 0 84 | ro = 0 85 | 86 | ;------------------------------>nandd, android real rootfs 87 | [partition3] 88 | class_name = DISK 89 | name = system 90 | size_hi = 0 91 | size_lo = 524288 92 | user_type = 0 93 | ro = 0 94 | 95 | ;------------------------------>nande, user data 96 | [partition4] 97 | class_name = DISK 98 | name = data 99 | size_hi = 0 100 | size_lo = 1228800 101 | user_type = 1 102 | ro = 0 103 | 104 | ;------------------------------>nandf, misc 105 | [partition5] 106 | class_name = DISK 107 | name = misc 108 | size_hi = 0 109 | size_lo = 16384 110 | user_type = 0 111 | ro = 0 112 | 113 | ;------------------------------>nandg, for recovery 114 | [partition6] 115 | class_name = DISK 116 | name = recovery 117 | size_hi = 0 118 | size_lo = 32768 119 | user_type = 0 120 | ro = 0 121 | 122 | ;------------------------------>nandh, android app cache 123 | [partition7] 124 | class_name = DISK 125 | name = cache 126 | size_hi = 0 127 | size_lo = 327680 128 | user_type = 0 129 | ro = 0 130 | 131 | ;------------------------------>nandi, data image backup 132 | [partition8] 133 | class_name = DISK 134 | name = databk 135 | size_hi = 0 136 | size_lo = 262144 137 | user_type = 0 138 | ro = 0 139 | 140 | ;******************************************************************************************************************** 141 | ; ąĚĽţ°üĎÂÔŘλÖĂ Łş °ŃÄł¸öąĚĽţ°üĎÂÔص˝Ö¸¶¨·ÖÇř, ĎÂÔŘ´ÎĘý×î´óÎŞ7´Î 142 | ; 143 | ;[downloadi] //ąĚĽţ°ü0ĎÂÔŘλÖĂ 144 | ; part_name = SYSDATAFS //·ÖÇřĂű 145 | ; pkt_name = ROOTFS_000000000 //ąĚĽţ°üĂűłĆ 146 | ; verify_file = VERIFY_000000000 //ĐŁŃéÎÄĽţĂűłĆ 147 | ; 148 | ; ×˘Łş1ˇ˘ downloadi ±íĘľµÚi´ÎĎÂÔع̼ţ, download Ψһ, Ç벻ҪËćŇâĐŢ¸Ä 149 | ; 2ˇ˘ part_num ÎŞ·ÖÇřşĹ, Ľ´ąĚĽţĎÂÔصÄλÖĂ 150 | ; 3ˇ˘ pkt_name ąĚĽţ°üĂűłĆ. ×î´ół¤¶ČÎŞ16byte, ÇŇĂűłĆąĚ¶¨. żÉ˛ÎżĽ"..\\ePDK\workspace\suni\liveclick\image.cfg" 151 | ; 4ˇ˘ verify_file ÎŞĐŁŃéÎÄĽţĂűłĆ. żÉ˛ÎżĽ"..\\ePDK\workspace\suni\liveclick\image.cfg" 152 | ; 5ˇ˘ pkt_name ÄÚČݲ»Ěî, ±íĘľ±ľ´ÎĎÂÔŘȡĎű. verify_fileÄÚČݲ»Ěî, ±íĘľ˛»ĐčŇŞĐŁŃé. 153 | ; 154 | ; Č磺Ҫ°ŃąĚĽţ°üzdisk.img, ĎÂÔŘÔÚµ˝SYSDATAFS·ÖÇřŔĆäĹäÖĂÎŞŁş 155 | ; [download0] 156 | ; part_name = SYSDATAFS 157 | ; pkt_name = ROOTFS_000000000 158 | ; verify_file = VERIFY_000000000 159 | ;******************************************************************************************************************** 160 | [down_num] 161 | down_num = 6 162 | 163 | [download0] 164 | part_name = bootloader 165 | pkt_name = BOOTLOADER_00000 166 | encrypt = 0 167 | verify_file = VBOOTLOADER_0000 168 | 169 | [download1] 170 | part_name = env 171 | pkt_name = ENVIROMENT_00000 172 | encrypt = 0 173 | verify_file = VENVIROMENT_0000 174 | 175 | [download2] 176 | part_name = boot 177 | pkt_name = BOOT_00000000000 178 | encrypt = 0 179 | verify_file = VBOOT_0000000000 180 | 181 | [download3] 182 | part_name = system 183 | pkt_name = SYSTEM_000000000 184 | encrypt = 0 185 | verify_file = VSYSTEMFS_000000 186 | 187 | [download4] 188 | part_name = recovery 189 | pkt_name = RECOVERY_0000000 190 | encrypt = 0 191 | verify_file = VRECOVERYFS_0000 192 | 193 | [download5] 194 | part_name = UDISK 195 | pkt_name = DISKFS_000000000 196 | encrypt = 0 197 | -------------------------------------------------------------------------------- /spec/assets/sys_para.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankowskib/FELix/3101ba0b1f04f8e359d95d5b11db8950e2883af6/spec/assets/sys_para.sample -------------------------------------------------------------------------------- /spec/assets/u-boot.sample: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankowskib/FELix/3101ba0b1f04f8e359d95d5b11db8950e2883af6/spec/assets/u-boot.sample -------------------------------------------------------------------------------- /spec/felix_commands_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | $FELIX_TEST_CASE = true 3 | load "#{File.dirname(__FILE__)}/../felix" 4 | 5 | describe FELix do 6 | context "when device is connected" do 7 | context "FEL" do 8 | 9 | before(:each) do 10 | devices = LIBUSB::Context.new.devices(:idVendor => 0x1f3a, 11 | :idProduct => 0xefe8) 12 | raise "Please connect device in FEL mode" if devices.empty? 13 | @fel = FELix.new(devices[0]) 14 | end 15 | 16 | after(:each) do 17 | @fel.bailout if @fel 18 | end 19 | 20 | describe "#get_device_status" do 21 | it "has a device in FEL mode" do 22 | expect(@fel.get_device_status.mode).to eq(AWDeviceMode[:fel]) 23 | end 24 | end 25 | 26 | describe "#read" do 27 | it "reads small chunk of data" do 28 | @fel.read(0x7E00, 256) 29 | end 30 | it "reads big chunk of data (last block is incomplete)" do 31 | @fel.read(0x7E00, (1 + rand(3)) + rand(FELIX_MAX_CHUNK)) 32 | end 33 | end 34 | 35 | describe "#write" do 36 | it "writes small chunk of data" do 37 | chunk = (0...256).map{65.+(rand(25)).chr}.join 38 | @fel.write(0x7E00, chunk) 39 | end 40 | end 41 | 42 | describe "#read/#write" do 43 | it "writes and reads small chunk of data" do 44 | chunk = (0...256).map{65.+(rand(25)).chr}.join 45 | @fel.write(0x7E00, chunk) 46 | data = @fel.read(0x7E00, chunk.length) 47 | expect(data.length).to eq(chunk.length) 48 | a_crc = Crc32.calculate(chunk, chunk.length, 0) 49 | b_crc = Crc32.calculate(data, data.length, 0) 50 | expect(a_crc).to eq(b_crc) 51 | end 52 | end 53 | 54 | describe "#run" do 55 | # @TODO: It'd be hard to figure out universal test for that 56 | end 57 | 58 | end # FEL commands 59 | end # device is connected 60 | end 61 | -------------------------------------------------------------------------------- /spec/felix_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | describe FELHelpers do 3 | describe '::checksum' do 4 | it 'passes checksum generation test for env' do 5 | File.open("./spec/assets/env.sample", "rb") do |f| 6 | expect(FELHelpers.checksum(f.read)).to eq (0x2ae1d857) 7 | end 8 | end 9 | 10 | it 'passes checksum generation test for bootloader' do 11 | File.open("./spec/assets/bootloader.sample", "rb") do |f| 12 | expect(FELHelpers.checksum(f.read)).to eq (0x93b56219) 13 | end 14 | end 15 | 16 | it 'recomputes crc for u-boot binary' do 17 | uboot = UbootBinary.read(File.read("./spec/assets/u-boot.sample")) 18 | old_sum = uboot.header.check_sum 19 | uboot.header.check_sum = UBOOT_STAMP_VALUE 20 | uboot.header.check_sum = FELHelpers.checksum(uboot.to_binary_s) 21 | expect(uboot.header.check_sum).to eq(old_sum) 22 | end 23 | 24 | end 25 | 26 | describe '::port_to_id' do 27 | it 'decodes port:PB22<2><1>' do 28 | expect(FELHelpers.port_to_id("port:PB22<2><1>")). 29 | to eq (0x7C4AC1) 30 | end 31 | it 'decodes port:PH20<2><1>' do 32 | expect(FELHelpers.port_to_id("port:PH20<2><1>")). 33 | to eq (0x7C4A87) 34 | end 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /spec/felix_struct_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | describe AWSysParaItem do 3 | it "generates valid structure" do 4 | para = AWSysParaItem.new 5 | expect(para.name).to eq("") 6 | expect(para.filename).to eq("") 7 | expect(para.verify_filename).to eq("") 8 | expect(para.encrypt).to eq(1) 9 | 10 | expect(para.to_binary_s.bytesize).to eq(97) 11 | end 12 | 13 | it "generates structure using AWDownloadItem" do 14 | dl = AWDownloadItem.new 15 | dl.name = "system" 16 | dl.address_low = 131072 17 | dl.lenlo = 2097152 18 | dl.filename = "SYSTEM_FEX000000" 19 | dl.verify_filename = "VSYSTEM_FEX00000" 20 | 21 | para = AWSysParaItem.new(dl) 22 | expect(para.name).to eq("system") 23 | expect(para.filename).to eq("SYSTEM_FEX000000") 24 | expect(para.verify_filename).to eq("VSYSTEM_FEX00000") 25 | 26 | end 27 | 28 | it "generates structure using AWLegacyDownloadItem" do 29 | dl = AWLegacyDownloadItem.new 30 | dl.name = "system" 31 | dl.address_low = 131072 32 | dl.lenlo = 2097152 33 | dl.filename = "SYSTEM_FEX000000" 34 | dl.verify_filename = "VSYSTEM_FEX00000" 35 | 36 | para = AWSysParaItem.new(dl) 37 | expect(para.name).to eq("system") 38 | expect(para.filename).to eq("SYSTEM_FEX000000") 39 | expect(para.verify_filename).to eq("VSYSTEM_FEX00000") 40 | end 41 | 42 | it "generates valid structure from sys_config.fex" do 43 | sys_config = File.read("./spec/assets/sys_config.sample") 44 | valid_parts = File.read("./spec/assets/sys_para.sample", 6*97, 0xA8C) # 6 items 45 | parts = BinData::Array.new(:type => :aw_sys_para_item, :initial_length => 6) 46 | 47 | cfg_ini = IniFile.new( :content => sys_config, :encoding => "UTF-8") 48 | 6.times do |n| 49 | parts[n] = AWSysParaItem.new( 50 | { 51 | :name => cfg_ini["download#{n}"]["part_name"], 52 | :filename => cfg_ini["download#{n}"]["pkt_name"], 53 | :encrypt => cfg_ini["download#{n}"]["encrypt"], 54 | :verify_filename => cfg_ini["download#{n}"]["verify_file"], 55 | }) 56 | end 57 | 58 | expect(parts.to_binary_s).to eq(valid_parts) 59 | end 60 | 61 | end 62 | 63 | describe AWSysParaPart do 64 | it "generates valid structure" do 65 | para = AWSysParaPart.new 66 | expect(para.address_low).to eq(0) 67 | expect(para.address_high).to eq(0) 68 | expect(para.classname).to eq("DISK") 69 | expect(para.name).to eq("") 70 | expect(para.user_type).to eq(0) 71 | expect(para.ro).to eq(0) 72 | expect(para.reserved).to eq(Array.new(24,0)) 73 | expect(para.to_binary_s.bytesize).to eq(104) 74 | end 75 | 76 | it "generates structure using AWSunxiPartition" do 77 | part = AWSunxiPartition.new 78 | part.address_low = 0x400 79 | part.name = "bootloader" 80 | part.user_type = 0 81 | part.ro = 0 82 | 83 | para = AWSysParaPart.new(part) 84 | expect(para.address_low).to eq(0x400) 85 | expect(para.address_high).to eq(0) 86 | expect(para.name).to eq("bootloader") 87 | expect(para.user_type).to eq(0) 88 | expect(para.ro).to eq(0) 89 | end 90 | 91 | it "generates structure using AWSunxiLegacyPartition" do 92 | part = AWSunxiLegacyPartition.new 93 | part.address_low = 0x400 94 | part.name = "bootloader" 95 | part.user_type = 0 96 | part.ro = 0 97 | 98 | para = AWSysParaPart.new(part) 99 | expect(para.address_low).to eq(0x400) 100 | expect(para.address_high).to eq(0) 101 | expect(para.name).to eq("bootloader") 102 | expect(para.user_type).to eq(0) 103 | expect(para.ro).to eq(0) 104 | end 105 | 106 | it "generates valid structure from sys_config.fex" do 107 | sys_config = File.read("./spec/assets/sys_config.sample") 108 | valid_parts = File.read("./spec/assets/sys_para.sample", 9*104, 0x4D8) # 9 partitions 109 | parts = BinData::Array.new(:type => :aw_sys_para_part, :initial_length => 9) 110 | 111 | cfg_ini = IniFile.new( :content => sys_config, :encoding => "UTF-8") 112 | 9.times do |n| 113 | parts[n] = AWSysParaPart.new( 114 | { 115 | :address_high => cfg_ini["partition#{n}"]["size_hi"], 116 | :address_low => cfg_ini["partition#{n}"]["size_lo"], 117 | :classname => cfg_ini["partition#{n}"]["class_name"], 118 | :name => cfg_ini["partition#{n}"]["name"], 119 | :user_type => cfg_ini["partition#{n}"]["user_type"], 120 | :ro => cfg_ini["partition#{n}"]["ro"] 121 | }) 122 | end 123 | 124 | expect(parts.to_binary_s).to eq(valid_parts) 125 | end 126 | 127 | end 128 | 129 | describe AWSysPara do 130 | it "generates valid structure" do 131 | sys = AWSysPara.new 132 | sys.part_num = 9 133 | sys.dl_num = 6 134 | expect(sys.to_binary_s.bytesize).to eq(5496) 135 | end 136 | 137 | it "creates structure using sys_config.fex, sys_config1.fex" do 138 | # load assets 139 | valid_para = File.read("./spec/assets/sys_para.sample") 140 | sys_config = File.read("./spec/assets/sys_config.sample") 141 | sys_config << File.read("./spec/assets/sys_config1.sample") 142 | 143 | expect(valid_para.bytesize).to eq(5496) 144 | sys = AWSysPara.new 145 | sys.dram = FELHelpers.create_dram_config(sys_config, true) 146 | cfg_ini = IniFile.new( :content => sys_config, :encoding => "UTF-8") 147 | sys.part_num = cfg_ini[:part_num]["num"] 148 | sys.part_num.times do |n| 149 | sys.part_items[n] = AWSysParaPart.new( 150 | { 151 | :address_high => cfg_ini["partition#{n}"]["size_hi"], 152 | :address_low => cfg_ini["partition#{n}"]["size_lo"], 153 | :classname => cfg_ini["partition#{n}"]["class_name"], 154 | :name => cfg_ini["partition#{n}"]["name"], 155 | :user_type => cfg_ini["partition#{n}"]["user_type"], 156 | :ro => cfg_ini["partition#{n}"]["ro"] 157 | }) 158 | end 159 | sys.dl_num = cfg_ini[:down_num]["down_num"] 160 | sys.dl_num.times do |n| 161 | sys.dl_items[n] = AWSysParaItem.new( 162 | { 163 | :name => cfg_ini["download#{n}"]["part_name"], 164 | :filename => cfg_ini["download#{n}"]["pkt_name"], 165 | :encrypt => cfg_ini["download#{n}"]["encrypt"], 166 | :verify_filename => cfg_ini["download#{n}"]["verify_file"], 167 | }) 168 | end 169 | 170 | # expect we created the same struct as binary one 171 | expect(sys.to_binary_s.bytesize).to eq(5496) 172 | expect(sys.to_binary_s).to eq(valid_para.b) 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /spec/libsparse_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | describe SparseImage do 3 | describe '#new' do 4 | it 'parses a sparse file #1' do 5 | File.open("./spec/assets/sparse1.sample") do |f| 6 | image = SparseImage.new(f) 7 | expect(image.get_final_size).to eq(512*1024*1024) 8 | expect(image.count_chunks).to eq(22) 9 | end 10 | end 11 | 12 | 13 | end 14 | 15 | describe '::is_valid?' do 16 | it 'validates file #1 as correct' do 17 | File.open("./spec/assets/sparse1.sample") do |f| 18 | expect(SparseImage.is_valid?(f.read(64))).to be(true) 19 | end 20 | end 21 | 22 | it 'validates random data as incorrect' do 23 | sample = (0...64).map{65.+(rand(25)).chr}.join 24 | expect(SparseImage.is_valid?(sample)).to be(false) 25 | end 26 | 27 | it 'validates random data with short size as incorrect' do 28 | sample = (0...2).map{65.+(rand(25)).chr}.join 29 | expect(SparseImage.is_valid?(sample)).to be(false) 30 | end 31 | 32 | end 33 | 34 | describe '#each_chunk' do 35 | it 'decompresses correctly a sparse file #1' do 36 | File.open("./spec/assets/sparse1.sample", "rb") do |f| 37 | image = SparseImage.new(f) 38 | #data = String.new 39 | crc = 0 40 | image.each_chunk do |chunk| 41 | crc = Crc32.calculate(chunk, chunk.length, crc) 42 | end 43 | expect(crc).to eq(0xC7B4BA44) 44 | end 45 | end 46 | 47 | end 48 | 49 | describe '#[]' do 50 | it 'gets first chunk of image' do 51 | File.open("./spec/assets/sparse1.sample", "rb") do |f| 52 | image = SparseImage.new(f) 53 | data = image[0] 54 | expect(Crc32.calculate(data, data.length, 0)).to eq(0x921FECBC) 55 | end 56 | end 57 | it 'gets last chunk of image' do 58 | File.open("./spec/assets/sparse1.sample", "rb") do |f| 59 | image = SparseImage.new(f) 60 | data = image[image.count_chunks - 1] 61 | expect(Crc32.calculate(data, data.length, 0)).to eq(0x61A31E82) 62 | end 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'hex_string' 2 | require 'hexdump' 3 | require 'colorize' 4 | require 'optparse' 5 | require 'libusb' 6 | require 'bindata' 7 | require 'crc32' 8 | require 'rc6' 9 | require 'thread' 10 | require 'inifile' 11 | require 'fileutils' 12 | require 'stringio' 13 | 14 | require_relative '../FELConsts' 15 | require_relative '../FELStructs' 16 | require_relative '../FELHelpers' 17 | require_relative '../FELCmds' 18 | require_relative '../libsparse' 19 | 20 | RSpec.configure do |config| 21 | # rspec-expectations config goes here. You can use an alternate 22 | # assertion/expectation library such as wrong or the stdlib/minitest 23 | # assertions if you prefer. 24 | config.expect_with :rspec do |expectations| 25 | # This option will default to `true` in RSpec 4. It makes the `description` 26 | # and `failure_message` of custom matchers include text for helper methods 27 | # defined using `chain`, e.g.: 28 | # be_bigger_than(2).and_smaller_than(4).description 29 | # # => "be bigger than 2 and smaller than 4" 30 | # ...rather than: 31 | # # => "be bigger than 2" 32 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 33 | end 34 | 35 | # rspec-mocks config goes here. You can use an alternate test double 36 | # library (such as bogus or mocha) by changing the `mock_with` option here. 37 | config.mock_with :rspec do |mocks| 38 | # Prevents you from mocking or stubbing a method that does not exist on 39 | # a real object. This is generally recommended, and will default to 40 | # `true` in RSpec 4. 41 | mocks.verify_partial_doubles = true 42 | end 43 | 44 | # The settings below are suggested to provide a good initial experience 45 | # with RSpec, but feel free to customize to your heart's content. 46 | =begin 47 | # These two settings work together to allow you to limit a spec run 48 | # to individual examples or groups you care about by tagging them with 49 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 50 | # get run. 51 | config.filter_run :focus 52 | config.run_all_when_everything_filtered = true 53 | 54 | # Limits the available syntax to the non-monkey patched syntax that is recommended. 55 | # For more details, see: 56 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 57 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 58 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 59 | config.disable_monkey_patching! 60 | 61 | # This setting enables warnings. It's recommended, but in some cases may 62 | # be too noisy due to issues in dependencies. 63 | config.warnings = true 64 | 65 | # Many RSpec users commonly either run the entire suite or an individual 66 | # file, and it's useful to allow more verbose output when running an 67 | # individual spec file. 68 | if config.files_to_run.one? 69 | # Use the documentation formatter for detailed output, 70 | # unless a formatter has already been configured 71 | # (e.g. via a command-line flag). 72 | config.default_formatter = 'doc' 73 | end 74 | 75 | # Print the 10 slowest examples and example groups at the 76 | # end of the spec run, to help surface which specs are running 77 | # particularly slow. 78 | config.profile_examples = 10 79 | 80 | # Run specs in random order to surface order dependencies. If you find an 81 | # order dependency and want to debug it, you can fix the order by providing 82 | # the seed, which is printed after each run. 83 | # --seed 1234 84 | config.order = :random 85 | 86 | # Seed global randomization in this process using the `--seed` CLI option. 87 | # Setting this allows you to use `--seed` to deterministically reproduce 88 | # test failures related to randomization by passing the same `--seed` value 89 | # as the one that triggered the failure. 90 | Kernel.srand config.seed 91 | =end 92 | end 93 | --------------------------------------------------------------------------------