diff --git a/kexts/drivers/block/ahci/ahci.h b/kexts/drivers/block/ahci/ahci.h new file mode 100644 index 0000000..f2e35bc --- /dev/null +++ b/kexts/drivers/block/ahci/ahci.h @@ -0,0 +1,631 @@ +#ifndef _AHCI_H_ +#define _AHCI_H_ + +#include +#include + +#define SATA_SIG_ATA 0x00000101 // SATA drive +#define SATA_SIG_ATAPI 0xEB140101 // SATAPI drive +#define SATA_SIG_SEMB 0xC33C0101 // Enclosure management bridge +#define SATA_SIG_PM 0x96690101 // Port multiplier + +#define AHCI_DEV_NULL 0 +#define AHCI_DEV_SATA 1 +#define AHCI_DEV_SEMB 2 +#define AHCI_DEV_PM 3 +#define AHCI_DEV_SATAPI 4 + +#define HBA_PORT_IPM_ACTIVE 1 +#define HBA_PORT_DET_PRESENT 3 + +#define AHCI_BASE 0x400000 // 4M + +#define HBA_PxCMD_ST 0x0001 +#define HBA_PxCMD_FRE 0x0010 +#define HBA_PxCMD_FR 0x4000 +#define HBA_PxCMD_CR 0x8000 + +#define HBA_PxIS_TFES 0x40000000 + +#define ATA_CMD_READ_DMA_EX 0x25u +#define ATA_CMD_IDENTIFY_DEVICE 0xECu + +enum fis_type { + FIS_TYPE_REG_H2D = 0x27u, + FIS_TYPE_REG_D2H = 0x34u, + FIS_TYPE_DMA_ACT = 0x39u, + FIS_TYPE_DMA_SETUP = 0x41u, + FIS_TYPE_DATA = 0x46u, + FIS_TYPE_BIST = 0x58u, + FIS_TYPE_PIO_SETUP = 0x5F, + FIS_TYPE_DEV_BITS = 0xA1, +}; + +struct fis_reg_h2d { + uint8_t fis_type; + + uint8_t pmport : 4; + uint8_t rsv0 : 3; + uint8_t c : 1; + + uint8_t command; + uint8_t feature_l; + + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t device; + + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t feature_h; + + uint8_t count_l; + uint8_t count_h; + uint8_t icc; + uint8_t control; + + uint8_t rsv1[4]; +} __packed; + +struct fis_reg_d2h { + uint8_t fis_type; + + uint8_t pmport : 4; + uint8_t rsv0 : 2; + uint8_t i : 1; + uint8_t rsv1 : 1; + + uint8_t status; + uint8_t error; + + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t device; + + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t rsv2; + + uint8_t count_l; + uint8_t count_h; + uint8_t rsv3[2]; + + uint8_t rsv4[4]; +} __packed; + +struct fis_data { + uint8_t fis_type; + + uint8_t pmport : 4; + uint8_t rsv0 : 4; + + uint8_t rsv1[2]; + + // data +} __packed; + +struct fis_pio_setup { + uint8_t fis_type; + + uint8_t pmport : 4; + uint8_t rsv0 : 1; + uint8_t d : 1; + uint8_t i : 1; + uint8_t rsv1 : 1; + + uint8_t status; + uint8_t error; + + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t device; + + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t rsv2; + + uint8_t count_l; + uint8_t count_h; + uint8_t rsv3; + uint8_t e_status; + + uint16_t rc; + uint8_t rsv4[2]; +} __packed; + +struct fis_dma_setup { + uint8_t fis_type; + + uint8_t pmport : 4; + uint8_t rsv0 : 1; + uint8_t d : 1; + uint8_t i : 1; + uint8_t a : 1; + + uint8_t rsv1[2]; + + uint64_t dma_buffer_id; + + uint32_t rsv2; + + uint32_t dba_buffer_offset; + + uint32_t dma_transfer_size; + + uint32_t rsv3; +} __packed; + +struct hba_cmd_header { + uint8_t cfl : 5; + uint8_t a : 1; + uint8_t w : 1; + uint8_t p : 1; + + uint8_t r : 1; + uint8_t b : 1; + uint8_t c : 1; + uint8_t rsv0 : 1; + uint8_t pmp : 4; + + uint16_t prdtl; + + volatile uint32_t prdbc; + + uint32_t ctba; + uint32_t ctbau; + + uint32_t rsv1[4]; +} __packed; + +struct hba_prdt_entry { + uint32_t dba; + uint32_t dbau; + uint32_t rsv0; + + uint32_t dbc : 22; + uint32_t rsv1 : 9; + uint32_t i : 1; +} __packed; + +struct hba_cmd_table { + uint8_t cfis[64]; + + uint8_t acmd[16]; + + uint8_t rsv[48]; + + struct hba_prdt_entry prdt_entry[]; +} __packed; + +struct hba_port +{ + uint32_t clb; + uint32_t clbu; + uint32_t fb; + uint32_t fbu; + uint32_t is; + uint32_t ie; + uint32_t cmd; + uint32_t rsv0; + uint32_t tfd; + uint32_t sig; + uint32_t ssts; + uint32_t sctl; + uint32_t serr; + uint32_t sact; + uint32_t ci; + uint32_t sntf; + uint32_t fbs; + uint32_t rsv1[11]; + uint32_t vendor[4]; +} __packed; + +struct hba_config { + uint32_t cap; + uint32_t ghc; + uint32_t is; + uint32_t pi; + uint32_t vs; + uint32_t ccc_ctl; + uint32_t ccc_ptl; + uint32_t em_loc; + uint32_t em_ctl; + uint32_t cap2; + uint32_t bohc; + + uint8_t rsv[116]; + uint8_t vendor[96]; + + struct hba_port ports[32]; +} __packed; + +struct ata_identify_device_data { + uint16_t g_config; + uint8_t rsv0[2]; + uint16_t s_config; + uint8_t rsv1[14]; + uint8_t serial_number[20]; + uint8_t rsv2[6]; + uint32_t firmware; + char model[40]; +} __packed; + +struct identify_device_data { + struct { + uint16_t reserved_1 : 1; + uint16_t retired_3 : 1; + uint16_t response_incomplete : 1; + uint16_t retired_2 : 3; + uint16_t fixed_device : 1; + uint16_t removable_media : 1; + uint16_t retired_1 : 7; + uint16_t device_type : 1; + } general_config; + uint16_t num_cylinders; + uint16_t specific_configuration; + uint16_t num_heads; + uint16_t retired_1[2]; + uint16_t num_sectors_per_track; + uint16_t vendor_unique_1[3]; + uint8_t serial_number[20]; + uint16_t retired_2[2]; + uint16_t obsolete_1; + uint8_t firmware_revision[8]; + uint8_t model_number[40]; + uint8_t maximum_block_transfer; + uint8_t vendor_unique_2; + struct { + uint16_t feature_supported : 1; + uint16_t reserved : 15; + } trusted_computing; + struct { + uint16_t reserved_word_50; + uint8_t current_long_physical_sector_alignment : 2; + uint8_t reserved_byte_49 : 6; + uint8_t dma_supported : 1; + uint8_t lba_supported : 1; + uint8_t iordy_disable : 1; + uint8_t iordy_supported : 1; + uint8_t reserved_1 : 1; + uint8_t standyby_timer_support : 1; + uint8_t reserved_2 : 2; + } capabilities; + uint16_t obsolete_words_51[2]; + uint16_t translation_fields_valid : 3; + uint16_t reserved_3 : 5; + uint16_t free_fall_control_sensitivity : 8; + uint16_t number_of_current_cylinders; + uint16_t number_of_current_heads; + uint16_t current_sectors_per_track; + uint32_t current_sector_capacity; + uint8_t current_multi_sector_setting; + uint8_t multi_sector_setting_valid : 1; + uint8_t reserved_byte_59 : 3; + uint8_t sanitize_feature_supported : 1; + uint8_t crypto_scramble_ext_command_supported : 1; + uint8_t overwrite_ext_command_supported : 1; + uint8_t block_erase_ext_command_supported : 1; + uint32_t user_addressable_sectors; + uint16_t obsolete_word_62; + uint16_t multi_word_dma_support : 8; + uint16_t multi_word_dma_active : 8; + uint16_t advanced_pio_modes : 8; + uint16_t reserved_byte_64 : 8; + uint16_t minimum_mw_xfer_cycle_time; + uint16_t recommended_mw_xfer_cycle_time; + uint16_t minimum_pio_cycle_time; + uint16_t minimum_pio_cycle_time_iordy; + struct { + uint16_t zoned_capabilities : 2; + uint16_t non_volatile_write_cache : 1; + uint16_t extended_user_addressable_sectors_supported : 1; + uint16_t device_encrypts_all_user_data : 1; + uint16_t read_zero_after_trim_supported : 1; + uint16_t optional_28_bit_commands_supported : 1; + uint16_t ieee_1667 : 1; + uint16_t download_microcode_dma_supported : 1; + uint16_t set_max_set_password_unlock_dma_supported : 1; + uint16_t write_buffer_dma_supported : 1; + uint16_t read_buffer_dma_supported : 1; + uint16_t device_config_identify_set_dma_supported : 1; + uint16_t lpsaerc_supported : 1; + uint16_t deterministic_read_after_trim_supported : 1; + uint16_t c_fast_spec_supported : 1; + } additional_supported; + uint16_t reserved_words_70[5]; + uint16_t queue_depth : 2; + uint16_t reserved_word_75[11]; + struct { + uint16_t reserved_0 : 1; + uint16_t sata_gen_1 : 1; + uint16_t sata_gen_2 : 1; + uint16_t sata_gen_3 : 1; + uint16_t reserved_1 : 1; + uint16_t ncq : 1; + uint16_t hipm : 1; + uint16_t phy_events : 1; + uint16_t ncq_unload : 1; + uint16_t ncq_priority : 1; + uint16_t host_auto_ps : 1; + uint16_t device_auto_ps : 1; + uint16_t read_log_dma : 1; + uint16_t reserved_2 : 1; + uint16_t current_speed : 3; + uint16_t ncq_streaming : 1; + uint16_t ncq_queue_mgmt : 1; + uint16_t ncq_receive_send : 1; + uint16_t devsl_pto_reduced_pwr_state : 1; + uint16_t reserved_3 : 8; + } serial_ata_capabilities; + struct { + uint16_t reserved_0 : 1; + uint16_t non_zero_offsets : 1; + uint16_t dma_setup_auto_activate : 1; + uint16_t dipm : 1; + uint16_t in_order_data : 1; + uint16_t hardware_feature_control : 1; + uint16_t software_settings_preservation : 1; + uint16_t ncq_autosense : 1; + uint16_t devslp : 1; + uint16_t hybrid_information : 1; + uint16_t reserved_1 : 6; + } serial_ata_features_supported; + struct { + uint16_t reserved_0 : 1; + uint16_t non_zero_offsets : 1; + uint16_t dma_setup_auto_activate : 1; + uint16_t dipm : 1; + uint16_t in_order_data : 1; + uint16_t hardware_feature_control : 1; + uint16_t software_settings_preservation : 1; + uint16_t device_auto_ps : 1; + uint16_t devslp : 1; + uint16_t hybrid_information : 1; + uint16_t reserved_1 : 6; + } serial_ata_features_enabled; + uint16_t major_revision; + uint16_t minor_revision; + struct { + uint16_t smart_commands : 1; + uint16_t security_mode : 1; + uint16_t removable_media_feature : 1; + uint16_t power_management : 1; + uint16_t reserved_1 : 1; + uint16_t write_cache : 1; + uint16_t look_ahead : 1; + uint16_t release_interrupt : 1; + uint16_t service_interrupt : 1; + uint16_t device_reset : 1; + uint16_t host_protected_area : 1; + uint16_t obsolete_1 : 1; + uint16_t write_buffer : 1; + uint16_t read_buffer : 1; + uint16_t nop : 1; + uint16_t obsolete_2 : 1; + uint16_t download_microcode : 1; + uint16_t dma_queued : 1; + uint16_t cfa : 1; + uint16_t advanced_pm : 1; + uint16_t msn : 1; + uint16_t power_up_in_standby : 1; + uint16_t manual_power_up : 1; + uint16_t reserved_2 : 1; + uint16_t set_max : 1; + uint16_t acoustics : 1; + uint16_t big_lba : 1; + uint16_t device_config_overlay : 1; + uint16_t flush_cache : 1; + uint16_t flush_cache_ext : 1; + uint16_t word_valid_83 : 2; + uint16_t smart_error_log : 1; + uint16_t smart_self_test : 1; + uint16_t media_serial_number : 1; + uint16_t media_card_pass_through : 1; + uint16_t streaming_feature : 1; + uint16_t gp_logging : 1; + uint16_t write_fua : 1; + uint16_t write_queued_fua : 1; + uint16_t wwn_64_bit : 1; + uint16_t urg_read_stream : 1; + uint16_t urg_write_stream : 1; + uint16_t reserved_for_tech_report : 2; + uint16_t idle_with_unload_feature : 1; + uint16_t word_valid : 2; + } command_set_support; + struct { + uint16_t smart_commands : 1; + uint16_t security_mode : 1; + uint16_t removable_media_feature : 1; + uint16_t power_management : 1; + uint16_t reserved_1 : 1; + uint16_t write_cache : 1; + uint16_t look_ahead : 1; + uint16_t release_interrupt : 1; + uint16_t service_interrupt : 1; + uint16_t device_reset : 1; + uint16_t host_protected_area : 1; + uint16_t obsolete_1 : 1; + uint16_t write_buffer : 1; + uint16_t read_buffer : 1; + uint16_t nop : 1; + uint16_t obsolete_2 : 1; + uint16_t download_microcode : 1; + uint16_t dma_queued : 1; + uint16_t cfa : 1; + uint16_t advanced_pm : 1; + uint16_t msn : 1; + uint16_t power_up_in_standby : 1; + uint16_t manual_power_up : 1; + uint16_t reserved_2 : 1; + uint16_t set_max : 1; + uint16_t acoustics : 1; + uint16_t big_lba : 1; + uint16_t device_config_overlay : 1; + uint16_t flush_cache : 1; + uint16_t flush_cache_ext : 1; + uint16_t resrved_3 : 1; + uint16_t words_119_120_valid : 1; + uint16_t smart_error_log : 1; + uint16_t smart_self_test : 1; + uint16_t media_serial_number : 1; + uint16_t media_card_pass_through : 1; + uint16_t streaming_feature : 1; + uint16_t gp_logging : 1; + uint16_t write_fua : 1; + uint16_t write_queued_fua : 1; + uint16_t wwn_64_bit : 1; + uint16_t urg_read_stream : 1; + uint16_t urg_write_stream : 1; + uint16_t reserved_for_tech_report : 2; + uint16_t idle_with_unload_feature : 1; + uint16_t reserved_4 : 2; + } command_set_active; + uint16_t ultra_dma_support : 8; + uint16_t ultra_dma_active : 8; + struct { + uint16_t time_required : 15; + uint16_t extended_time_reported : 1; + } normal_security_erase_unit; + struct { + uint16_t time_required : 15; + uint16_t extended_time_reported : 1; + } enhanced_security_erase_unit; + uint16_t current_apm_level : 8; + uint16_t reserved_word_91 : 8; + uint16_t master_password_id; + uint16_t hardware_reset_result; + uint16_t current_acoustic_value : 8; + uint16_t recommended_acoustic_value : 8; + uint16_t stream_min_request_size; + uint16_t streaming_transfer_time_dma; + uint16_t streaming_access_latency_dmapio; + uint32_t streaming_perf_granularity; + uint32_t max_48_bit_lba_2; + uint16_t streaming_transfer_time; + uint16_t dsm_cap; + struct { + uint16_t logical_sectors_per_physical_sector : 4; + uint16_t reserved_0 : 8; + uint16_t logical_sector_longer_than_256_words : 1; + uint16_t multiple_logical_sectors_per_physical_sector : 1; + uint16_t reserved_1 : 2; + } physical_logical_sector_size; + uint16_t inter_seek_delay; + uint16_t world_wide_name[4]; + uint16_t reserved_for_world_wide_name_128[4]; + uint16_t reserved_for_tlc_technical_report; + uint16_t words_per_logical_sector[2]; + struct { + uint16_t reserved_for_drq_technical_report : 1; + uint16_t write_read_verify : 1; + uint16_t write_uncorrectable_ext : 1; + uint16_t read_write_log_dma_ext : 1; + uint16_t download_microcode_mode_3 : 1; + uint16_t freefall_control : 1; + uint16_t sense_data_reporting : 1; + uint16_t extended_power_conditions : 1; + uint16_t reserved_0 : 6; + uint16_t word_valid : 2; + } command_set_support_ext; + struct { + uint16_t reserved_for_drq_technical_report : 1; + uint16_t write_read_verify : 1; + uint16_t write_uncorrectable_ext : 1; + uint16_t read_write_log_dma_ext : 1; + uint16_t download_microcode_mode_3 : 1; + uint16_t freefall_control : 1; + uint16_t sense_data_reporting : 1; + uint16_t extended_power_conditions : 1; + uint16_t reserved_0 : 6; + uint16_t reserved_1 : 2; + } command_set_active_ext; + uint16_t reserved_for_expanded_supportand_active[6]; + uint16_t msn_support : 2; + uint16_t reserved_word_127 : 14; + struct { + uint16_t security_supported : 1; + uint16_t security_enabled : 1; + uint16_t security_locked : 1; + uint16_t security_frozen : 1; + uint16_t security_count_expired : 1; + uint16_t enhanced_security_erase_supported : 1; + uint16_t reserved_0 : 2; + uint16_t security_level : 1; + uint16_t reserved_1 : 7; + } security_status; + uint16_t reserved_word_129[31]; + struct { + uint16_t maximum_current_in_ma : 12; + uint16_t cfa_power_mode_1_disabled : 1; + uint16_t cfa_power_mode_1_required : 1; + uint16_t reserved_0 : 1; + uint16_t word_160_supported : 1; + } cfa_power_mode_1; + uint16_t reserved_for_cfa_word_161[7]; + uint16_t nominal_form_factor : 4; + uint16_t reserved_word_168 : 12; + struct { + uint16_t supports_trim : 1; + uint16_t reserved_0 : 15; + } data_set_management_feature; + uint16_t additional_product_id[4]; + uint16_t reserved_for_cfa_word_174[2]; + uint16_t current_media_serial_number[30]; + struct { + uint16_t supported : 1; + uint16_t reserved_0 : 1; + uint16_t write_same_suported : 1; + uint16_t error_recovery_control_supported : 1; + uint16_t feature_control_suported : 1; + uint16_t data_tables_suported : 1; + uint16_t reserved_1 : 6; + uint16_t vendor_specific : 4; + } sct_command_transport; + uint16_t reserved_word_207[2]; + struct { + uint16_t alignment_of_logical_within_physical : 14; + uint16_t word_209_supported : 1; + uint16_t reserved_0 : 1; + } block_alignment; + uint16_t write_read_verify_sector_count_mode_3_only[2]; + uint16_t write_read_verify_sector_count_mode_2_only[2]; + struct { + uint16_t nv_cache_power_mode_enabled : 1; + uint16_t reserved_0 : 3; + uint16_t nv_cache_feature_set_enabled : 1; + uint16_t reserved_1 : 3; + uint16_t nv_cache_power_mode_version : 4; + uint16_t nv_cache_feature_set_version : 4; + } nv_cache_capabilities; + uint16_t nv_cache_size_lsw; + uint16_t nv_cache_size_msw; + uint16_t nominal_media_rotation_rate; + uint16_t reserved_word_218; + struct { + uint8_t nv_cache_estimated_time_to_spin_up_in_seconds; + uint8_t reserved; + } nv_cache_options; + uint16_t write_read_verify_sector_count_mode : 8; + uint16_t reserved_word_220 : 8; + uint16_t reserved_word_221; + struct { + uint16_t major_version : 12; + uint16_t transport_type : 4; + } transport_major_version; + uint16_t transport_minor_version; + uint16_t reserved_word_224[6]; + uint32_t extended_number_of_user_addressable_sectors[2]; + uint16_t min_blocks_per_download_microcode_mode_03; + uint16_t max_blocks_per_download_microcode_mode_03; + uint16_t reserved_word_236[19]; + uint16_t signature : 8; + uint16_t check_sum : 8; +} __packed; + +#endif diff --git a/kexts/drivers/block/ahci/extension.yaml b/kexts/drivers/block/ahci/extension.yaml new file mode 100644 index 0000000..473e693 --- /dev/null +++ b/kexts/drivers/block/ahci/extension.yaml @@ -0,0 +1,8 @@ +name: ahci +description: | + AHCI (SATA) disk driver +id: net.doorstuck.socks.ahci +license: BSD-3-Clause +copyright: Copyright © Max Wash 2023 +sources: + - main.c diff --git a/kexts/drivers/block/ahci/main.c b/kexts/drivers/block/ahci/main.c new file mode 100644 index 0000000..eb98221 --- /dev/null +++ b/kexts/drivers/block/ahci/main.c @@ -0,0 +1,551 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ahci.h" + +struct ahci_device { + int port_no; + int type; + volatile struct hba_port *port; + + struct hba_cmd_header cmd_header[32]; + char fis_data[256]; +}; + +/************************** STOLEN **********************/ + +#define ATA_DEV_BUSY 0x80 +#define ATA_DEV_DRQ 0x08 + +// Find a free command list slot +int find_cmdslot(volatile struct hba_port *port) +{ + // If not set in SACT and CI, the slot is free + uint32_t slots = (port->sact | port->ci); + for (int i=0; i<32; i++) + { + if ((slots&1) == 0) + return i; + slots >>= 1; + } + printk("Cannot find free command list entry"); + return -1; +} + +static struct hba_cmd_table *create_cmd_table(struct iovec *vec, size_t nvec) +{ + size_t sz = sizeof(struct hba_cmd_table) + (sizeof(struct hba_prdt_entry) * nvec); + struct hba_cmd_table *out = kzalloc(sz, VM_NORMAL); + + for (size_t i = 0; i < nvec; i++) { + phys_addr_t vec_phys = vm_virt_to_phys(vec->io_buf); + out->prdt_entry[i].dba = vec_phys & 0xFFFFFFFF; + out->prdt_entry[i].dbau = (vec_phys >> 32) & 0xFFFFFFFF; + out->prdt_entry[i].dbc = vec->io_len - 1; + out->prdt_entry[i].i = (i == nvec - 1) ? 1 : 0; + } + + return out; +} + +static void free_cmd_table(struct hba_cmd_table *table) +{ + kfree(table); +} + +static kern_status_t send_command(struct ahci_device *dev, unsigned int cmd, struct iovec *vec, size_t nvec) +{ + if (nvec == 0) { + return KERN_OK; + } + + volatile struct hba_port *port = dev->port; + port->is = (uint32_t) -1; + int spin = 0; + int slot = find_cmdslot(port); + if (slot == -1) { + return KERN_BUSY; + } + + struct hba_cmd_header *cmdheader = &dev->cmd_header[slot]; + cmdheader->cfl = sizeof(struct fis_reg_h2d) / sizeof(uint32_t); + cmdheader->w = 0; + cmdheader->prdtl = nvec; + + struct hba_cmd_table *cmdtbl = create_cmd_table(vec, nvec); + phys_addr_t cmdtbl_phys = vm_virt_to_phys(cmdtbl); + cmdheader->ctba = cmdtbl_phys & 0xFFFFFFFF; + cmdheader->ctbau = (cmdtbl_phys >> 32) & 0xFFFFFFFF; + + struct fis_reg_h2d *cmdfis = (struct fis_reg_h2d*)(&cmdtbl->cfis); + + cmdfis->fis_type = FIS_TYPE_REG_H2D; + cmdfis->c = 1; + cmdfis->command = cmd; + + while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) { + spin++; + } + + if (spin == 1000000) { + printk("Port is hung"); + free_cmd_table(cmdtbl); + return KERN_DEVICE_STUCK; + } + + port->ci = 1 << slot; + + // Wait for completion + while (1) + { + if ((port->ci & (1 << slot)) == 0) { + break; + } + + if (port->is & HBA_PxIS_TFES) { + printk("Read disk error 1"); + free_cmd_table(cmdtbl); + return KERN_IO_ERROR; + } + } + + free_cmd_table(cmdtbl); + + if (port->is & HBA_PxIS_TFES) { + printk("Read disk error 2"); + return KERN_IO_ERROR; + } + + return KERN_OK; +} + +static kern_status_t identify_device(struct ahci_device *dev, struct identify_device_data *out) +{ + /* + port->is = (uint32_t) -1; // Clear pending interrupt bits + int spin = 0; // Spin lock timeout counter + int slot = find_cmdslot(port); + if (slot == -1) + return false; + + struct hba_cmd_header *cmdheader = vm_phys_to_virt(port->clb); + cmdheader += slot; + cmdheader->cfl = sizeof(struct fis_reg_h2d)/sizeof(uint32_t); // Command FIS size + cmdheader->w = 0; // Read from device + cmdheader->prdtl = 1; + + struct hba_cmd_table *cmdtbl = vm_phys_to_virt(cmdheader->ctba); + memset(cmdtbl, 0, sizeof(struct hba_cmd_table) + + (cmdheader->prdtl-1)*sizeof(struct hba_prdt_entry)); + + cmdtbl->prdt_entry[0].dba = (uint32_t)vm_virt_to_phys(out); + cmdtbl->prdt_entry[0].dbc = sizeof *out - 1; + cmdtbl->prdt_entry[0].i = 1; + + // Setup command + struct fis_reg_h2d *cmdfis = (struct fis_reg_h2d*)(&cmdtbl->cfis); + + cmdfis->fis_type = FIS_TYPE_REG_H2D; + cmdfis->c = 1; // Command + cmdfis->command = ATA_CMD_IDENTIFY_DEVICE; + + // The below loop waits until the port is no longer busy before issuing a new command + while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) + { + spin++; + } + if (spin == 1000000) + { + printk("Port is hung"); + return -1; + } + + port->ci = 1<ci & (1<is & HBA_PxIS_TFES) // Task file error + { + printk("Read disk error 1"); + return -1; + } + } + + // Check again + if (port->is & HBA_PxIS_TFES) + { + printk("Read disk error 2"); + return -1; + } + + return 0; + */ + struct iovec vec = { .io_buf = out, .io_len = sizeof *out }; + kern_status_t status = send_command(dev, ATA_CMD_IDENTIFY_DEVICE, &vec, 1); + if (status != KERN_OK) { + return status; + } + + out->model_number[39] = 0; + for (int i = 0; i < 40; i += 2) { + unsigned char c = out->model_number[i]; + out->model_number[i] = out->model_number[i + 1]; + out->model_number[i + 1] = c; + } + + for (int i = sizeof out->model_number - 1; i >= 0; i--) { + if (isspace(out->model_number[i]) || out->model_number[i] == 0) { + out->model_number[i] = 0; + } else { + break; + } + } + + return KERN_OK; +} + +// Start command engine +void start_cmd(volatile struct hba_port *port) +{ + // Wait until CR (bit15) is cleared + while (port->cmd & HBA_PxCMD_CR) + ; + + // Set FRE (bit4) and ST (bit0) + port->cmd |= HBA_PxCMD_FRE; + port->cmd |= HBA_PxCMD_ST; +} + +// Stop command engine +void stop_cmd(volatile struct hba_port *port) +{ + // Clear ST (bit0) + port->cmd &= ~HBA_PxCMD_ST; + + // Clear FRE (bit4) + port->cmd &= ~HBA_PxCMD_FRE; + + // Wait until FR (bit14), CR (bit15) are cleared + while(1) + { + if (port->cmd & HBA_PxCMD_FR) + continue; + if (port->cmd & HBA_PxCMD_CR) + continue; + break; + } + +} + +void port_rebase(struct ahci_device *dev) +{ + volatile struct hba_port *port = dev->port; + + stop_cmd(port); // Stop command engine + + struct hba_cmd_header *cmdheader = dev->cmd_header; + + // Command list offset: 1K*portno + // Command list entry size = 32 + // Command list entry maxim count = 32 + // Command list maxim size = 32*32 = 1K per port + phys_addr_t cmdheader_phys = vm_virt_to_phys(cmdheader); + port->clb = cmdheader_phys & 0xFFFFFFFF; + port->clbu = (cmdheader_phys >> 32) & 0xFFFFFFFF; + memset(cmdheader, 0, sizeof *cmdheader); + + // FIS offset: 32K+256*portno + // FIS entry size = 256 bytes per port + phys_addr_t fis_phys = vm_virt_to_phys(dev->fis_data); + port->fb = fis_phys & 0xFFFFFFFF; + port->fbu = (fis_phys >> 32) & 0xFFFFFFFF; + memset(dev->fis_data, 0, 256); + + /* + // Command table offset: 40K + 8K*portno + // Command table size = 256*32 = 8K per port + for (int i=0; i<32; i++) + { + cmdheader[i].prdtl = 8; // 8 prdt entries per command table + // 256 bytes per command table, 64+16+48+16*8 + // Command table offset: 40K + 8K*portno + cmdheader_index*256 + cmdheader[i].ctba = phys + offset; + cmdheader[i].ctbau = 0; + memset(virt + offset, 0, 256); + + offset += 256; + } + */ + + start_cmd(port); // Start command engine +} + +/* +static int read(struct hba_port *port, uint32_t startl, uint32_t starth, uint32_t count, uint16_t *buf) +{ + port->is = (uint32_t) -1; // Clear pending interrupt bits + int spin = 0; // Spin lock timeout counter + int slot = find_cmdslot(port); + if (slot == -1) + return false; + + struct hba_cmd_header *cmdheader = (struct hba_cmd_header*)(uintptr_t)port->clb; + cmdheader += slot; + cmdheader->cfl = sizeof(struct fis_reg_h2d)/sizeof(uint32_t); // Command FIS size + cmdheader->w = 0; // Read from device + cmdheader->prdtl = (uint16_t)((count-1)>>4) + 1; // PRDT entries count + + struct hba_cmd_table *cmdtbl = (struct hba_cmd_table*)((uintptr_t)cmdheader->ctba); + memset(cmdtbl, 0, sizeof(struct hba_cmd_table) + + (cmdheader->prdtl-1)*sizeof(struct hba_prdt_entry)); + + // 8K bytes (16 sectors) per PRDT + int i; + for (i=0; iprdtl-1; i++) + { + cmdtbl->prdt_entry[i].dba = (uint32_t)(uintptr_t)buf; + cmdtbl->prdt_entry[i].dbc = 8*1024-1; // 8K bytes (this value should always be set to 1 less than the actual value) + cmdtbl->prdt_entry[i].i = 1; + buf += 4*1024; // 4K words + count -= 16; // 16 sectors + } + // Last entry + cmdtbl->prdt_entry[i].dba = (uint32_t)(uintptr_t)buf; + cmdtbl->prdt_entry[i].dbc = (count<<9)-1; // 512 bytes per sector + cmdtbl->prdt_entry[i].i = 1; + + // Setup command + struct fis_reg_h2d *cmdfis = (struct fis_reg_h2d*)(&cmdtbl->cfis); + + cmdfis->fis_type = FIS_TYPE_REG_H2D; + cmdfis->c = 1; // Command + cmdfis->command = ATA_CMD_READ_DMA_EX; + + cmdfis->lba0 = (uint8_t)startl; + cmdfis->lba1 = (uint8_t)(startl>>8); + cmdfis->lba2 = (uint8_t)(startl>>16); + cmdfis->device = 1<<6; // LBA mode + + cmdfis->lba3 = (uint8_t)(startl>>24); + cmdfis->lba4 = (uint8_t)starth; + cmdfis->lba5 = (uint8_t)(starth>>8); + + cmdfis->count_l = count & 0xFF; + cmdfis->count_h = (count >> 8) & 0xFF; + + // The below loop waits until the port is no longer busy before issuing a new command + while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) + { + spin++; + } + if (spin == 1000000) + { + printk("Port is hung"); + return -1; + } + + port->ci = 1<ci & (1<is & HBA_PxIS_TFES) // Task file error + { + printk("Read disk error 1"); + return -1; + } + } + + // Check again + if (port->is & HBA_PxIS_TFES) + { + printk("Read disk error 2"); + return -1; + } + + return 0; +} +*/ + +static int check_type(volatile struct hba_port *port) +{ + uint32_t ssts = port->ssts; + + uint8_t ipm = (ssts >> 8) & 0x0F; + uint8_t det = ssts & 0x0F; + + if (det != HBA_PORT_DET_PRESENT) { + return AHCI_DEV_NULL; + } + + if (ipm != HBA_PORT_IPM_ACTIVE) { + return AHCI_DEV_NULL; + } + + switch (port->sig) { + case SATA_SIG_ATAPI: + return AHCI_DEV_SATAPI; + case SATA_SIG_SEMB: + return AHCI_DEV_SEMB; + case SATA_SIG_PM: + return AHCI_DEV_PM; + default: + return AHCI_DEV_SATA; + } +} + +void probe_ports(struct driver *driver, struct device *controller, volatile struct hba_config *abar) +{ + uint32_t pi = abar->pi; + for (int i = 0; i < 32; i++) { + if (!(pi & 1)) { + pi >>= 1; + continue; + } + + int dt = check_type(&abar->ports[i]); + + switch (dt) { + case AHCI_DEV_SATA: + case AHCI_DEV_SATAPI: + break; + default: + pi >>= 1; + continue; + } + + struct block_device *bdev = block_device_create(); + if (!bdev) { + continue; + } + + struct device *bdev_base = block_device_base(bdev); + + struct ahci_device *ahci_dev = kmalloc(sizeof *ahci_dev, VM_NORMAL); + if (!ahci_dev) { + device_deref(bdev_base); + continue; + } + + ahci_dev->port = &abar->ports[i]; + ahci_dev->port_no = i; + ahci_dev->type = dt; + bdev_base->dev_priv = ahci_dev; + port_rebase(ahci_dev); + + snprintf(bdev_base->dev_name, sizeof bdev_base->dev_name, "ahci%d", i); + + if (dt == AHCI_DEV_SATA) { + struct identify_device_data identity_data = {0}; + kern_status_t status = identify_device(ahci_dev, &identity_data); + if (status != KERN_OK) { + kfree(ahci_dev); + device_deref(bdev_base); + continue; + } + + /* + printk("ahci: found device '%s'", identity_data.model_number); + printk("ahci: version: %u.%u", identity_data.major_revision, identity_data.major_revision); + printk("ahci: C:%u, H:%u, S/T:%u", identity_data.num_cylinders, identity_data.num_heads, identity_data.num_sectors_per_track); + printk("ahci: nr sectors=%u", identity_data.user_addressable_sectors); + printk("ahci: offset: %u", offsetof(struct identify_device_data, current_sector_capacity)); + */ + + /* TODO read IDENTIFY DEVICE log to support non-512 sector sizes */ + bdev->sector_size = 512; + bdev->capacity = identity_data.user_addressable_sectors; + + snprintf(bdev_base->dev_model_name, sizeof bdev_base->dev_model_name, "%s", identity_data.model_number); + } + + device_register(bdev_base, driver, controller); + + pi >>= 1; + } +} + +/************************** STOLEN **********************/ + +static struct pci_driver *ahci_driver = NULL; +static volatile struct hba_config *hba_config = NULL; + +static int irq_callback(void); + +static struct irq_hook irq_hook = { + .irq_callback = irq_callback, +}; + +static struct pci_device_id ahci_device_ids[] = { + PCI_CLASS_ID(0x01, 0x06), + PCI_DEVICE_ID_INVALID, +}; + + +static kern_status_t ahci_scan(struct device *ahci_bus) +{ + printk("ahci: scanning connected SATA devices"); + uint32_t abar = pci_device_read_field(ahci_bus, PCI_REG_BAR5, 4); + hba_config = vm_phys_to_virt(abar); + probe_ports(pci_driver_base(ahci_driver), ahci_bus, hba_config); + + uint8_t irq = pci_device_read_field(ahci_bus, PCI_REG_INTERRUPT_LINE, 1); + printk("ahci: IRQ line: %u", irq); + return KERN_OK; +} + +static struct bus_device_ops ahci_bus_ops = { + .scan = ahci_scan, +}; + +static int irq_callback(void) +{ + printk("ahci: received IRQ"); + return 0; +} + +static kern_status_t ahci_probe(struct pci_driver *driver, struct device *dev) +{ + printk("ahci: found AHCI controller"); + struct bus_device *ahci_bus = bus_device_from_generic(dev); + ahci_bus->b_ops = &ahci_bus_ops; + snprintf(dev->dev_name, sizeof dev->dev_name, "ahci"); + + uint8_t irq_vec = pci_device_read_field(dev, PCI_REG_INTERRUPT_LINE, 1); + hook_irq(IRQ0 + irq_vec, &irq_hook); + + return device_register(dev, pci_driver_base(ahci_driver), NULL); +} + +static kern_status_t online(struct kext *self) +{ + printk("ahci: registering AHCI driver"); + ahci_driver = pci_driver_create(self, "ahci", ahci_device_ids); + if (!ahci_driver) { + return KERN_NO_MEMORY; + } + + ahci_driver->probe = ahci_probe; + + pci_driver_register(ahci_driver); + return KERN_OK; +} + +DEFINE_KEXT("net.doorstuck.socks.ahci", + online, NULL, + PCI_SUBSYSTEM_KEXT_ID);