Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_ahci.c
| Show First 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | |||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <assert.h> | #include <assert.h> | ||||
| #include <pthread.h> | #include <pthread.h> | ||||
| #include <pthread_np.h> | #include <pthread_np.h> | ||||
| #include <inttypes.h> | #include <inttypes.h> | ||||
| #include <md5.h> | #include <md5.h> | ||||
| #include "bhyverun.h" | #include "bhyverun.h" | ||||
| #include "config.h" | |||||
| #include "debug.h" | |||||
| #include "pci_emul.h" | #include "pci_emul.h" | ||||
| #include "ahci.h" | #include "ahci.h" | ||||
| #include "block_if.h" | #include "block_if.h" | ||||
| #define DEF_PORTS 6 /* Intel ICH8 AHCI supports 6 ports */ | #define DEF_PORTS 6 /* Intel ICH8 AHCI supports 6 ports */ | ||||
| #define MAX_PORTS 32 /* AHCI supports 32 ports */ | #define MAX_PORTS 32 /* AHCI supports 32 ports */ | ||||
| #define PxSIG_ATA 0x00000101 /* ATA drive */ | #define PxSIG_ATA 0x00000101 /* ATA drive */ | ||||
| ▲ Show 20 Lines • Show All 2,239 Lines • ▼ Show 20 Lines | pci_ahci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, | ||||
| } | } | ||||
| value >>= 8 * (regoff & 0x3); | value >>= 8 * (regoff & 0x3); | ||||
| pthread_mutex_unlock(&sc->mtx); | pthread_mutex_unlock(&sc->mtx); | ||||
| return (value); | return (value); | ||||
| } | } | ||||
| /* | |||||
| * Each AHCI controller has a "port" node which contains nodes for | |||||
| * each port named after the decimal number of the port (no leading | |||||
| * zeroes). Port nodes contain a "type" ("hd" or "cd"), as well as | |||||
| * options for blockif. For example: | |||||
| * | |||||
| * pci.0.1.0 | |||||
| * .device="ahci" | |||||
| * .port | |||||
| * .0 | |||||
| * .type="hd" | |||||
| * .path="/path/to/image" | |||||
| */ | |||||
| static int | static int | ||||
| pci_ahci_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts, int atapi) | pci_ahci_legacy_config_port(nvlist_t *nvl, int port, const char *type, | ||||
| const char *opts) | |||||
| { | { | ||||
| char node_name[sizeof("XX")]; | |||||
| nvlist_t *port_nvl; | |||||
| snprintf(node_name, sizeof(node_name), "%d", port); | |||||
| port_nvl = create_relative_config_node(nvl, node_name); | |||||
| set_config_value_node(port_nvl, "type", type); | |||||
| return (blockif_legacy_config(port_nvl, opts)); | |||||
| } | |||||
| static int | |||||
| pci_ahci_legacy_config(nvlist_t *nvl, const char *opts) | |||||
| { | |||||
| nvlist_t *ports_nvl; | |||||
| const char *type; | |||||
| char *next, *next2, *str, *tofree; | |||||
| int p, ret; | |||||
| if (opts == NULL) | |||||
| return (0); | |||||
| ports_nvl = create_relative_config_node(nvl, "port"); | |||||
| ret = 1; | |||||
| tofree = str = strdup(opts); | |||||
| for (p = 0; p < MAX_PORTS && str != NULL; p++, str = next) { | |||||
| /* Identify and cut off type of present port. */ | |||||
| if (strncmp(str, "hd:", 3) == 0) { | |||||
| type = "hd"; | |||||
| str += 3; | |||||
| } else if (strncmp(str, "cd:", 3) == 0) { | |||||
| type = "cd"; | |||||
| str += 3; | |||||
| } else | |||||
| type = NULL; | |||||
| /* Find and cut off the next port options. */ | |||||
| next = strstr(str, ",hd:"); | |||||
| next2 = strstr(str, ",cd:"); | |||||
| if (next == NULL || (next2 != NULL && next2 < next)) | |||||
| next = next2; | |||||
| if (next != NULL) { | |||||
| next[0] = 0; | |||||
| next++; | |||||
| } | |||||
| if (str[0] == 0) | |||||
| continue; | |||||
| if (type == NULL) { | |||||
| EPRINTLN("Missing or invalid type for port %d: \"%s\"", | |||||
| p, str); | |||||
| goto out; | |||||
| } | |||||
| if (pci_ahci_legacy_config_port(ports_nvl, p, type, str) != 0) | |||||
| goto out; | |||||
| } | |||||
| ret = 0; | |||||
| out: | |||||
| free(tofree); | |||||
| return (ret); | |||||
| } | |||||
| static int | |||||
| pci_ahci_cd_legacy_config(nvlist_t *nvl, const char *opts) | |||||
| { | |||||
| nvlist_t *ports_nvl; | |||||
| ports_nvl = create_relative_config_node(nvl, "port"); | |||||
| return (pci_ahci_legacy_config_port(ports_nvl, 0, "cd", opts)); | |||||
| } | |||||
| static int | |||||
| pci_ahci_hd_legacy_config(nvlist_t *nvl, const char *opts) | |||||
| { | |||||
| nvlist_t *ports_nvl; | |||||
| ports_nvl = create_relative_config_node(nvl, "port"); | |||||
| return (pci_ahci_legacy_config_port(ports_nvl, 0, "hd", opts)); | |||||
| } | |||||
| static int | |||||
| pci_ahci_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) | |||||
| { | |||||
| char bident[sizeof("XX:XX:XX")]; | char bident[sizeof("XX:XX:XX")]; | ||||
| char node_name[sizeof("XX")]; | |||||
| struct blockif_ctxt *bctxt; | struct blockif_ctxt *bctxt; | ||||
| struct pci_ahci_softc *sc; | struct pci_ahci_softc *sc; | ||||
| int ret, slots, p; | int atapi, ret, slots, p; | ||||
| MD5_CTX mdctx; | MD5_CTX mdctx; | ||||
| u_char digest[16]; | u_char digest[16]; | ||||
| char *next, *next2; | const char *path, *type, *value; | ||||
| char *bopt, *uopt, *xopts, *config; | nvlist_t *ports_nvl, *port_nvl; | ||||
| FILE* fp; | |||||
| size_t block_len; | |||||
| int comma, optpos; | |||||
| ret = 0; | ret = 0; | ||||
| #ifdef AHCI_DEBUG | #ifdef AHCI_DEBUG | ||||
| dbg = fopen("/tmp/log", "w+"); | dbg = fopen("/tmp/log", "w+"); | ||||
| #endif | #endif | ||||
| sc = calloc(1, sizeof(struct pci_ahci_softc)); | sc = calloc(1, sizeof(struct pci_ahci_softc)); | ||||
| pi->pi_arg = sc; | pi->pi_arg = sc; | ||||
| sc->asc_pi = pi; | sc->asc_pi = pi; | ||||
| pthread_mutex_init(&sc->mtx, NULL); | pthread_mutex_init(&sc->mtx, NULL); | ||||
| sc->ports = 0; | sc->ports = 0; | ||||
| sc->pi = 0; | sc->pi = 0; | ||||
| slots = 32; | slots = 32; | ||||
| for (p = 0; p < MAX_PORTS && opts != NULL; p++, opts = next) { | ports_nvl = find_relative_config_node(nvl, "port"); | ||||
| for (p = 0; p < MAX_PORTS; p++) { | |||||
| struct ata_params *ata_ident = &sc->port[p].ata_ident; | struct ata_params *ata_ident = &sc->port[p].ata_ident; | ||||
| memset(ata_ident, 0, sizeof(struct ata_params)); | char ident[AHCI_PORT_IDENT]; | ||||
| /* Identify and cut off type of present port. */ | snprintf(node_name, sizeof(node_name), "%d", p); | ||||
| if (strncmp(opts, "hd:", 3) == 0) { | port_nvl = find_relative_config_node(ports_nvl, node_name); | ||||
| atapi = 0; | if (port_nvl == NULL) | ||||
| opts += 3; | continue; | ||||
| } else if (strncmp(opts, "cd:", 3) == 0) { | |||||
| atapi = 1; | |||||
| opts += 3; | |||||
| } | |||||
| /* Find and cut off the next port options. */ | type = get_config_value_node(port_nvl, "type"); | ||||
| next = strstr(opts, ",hd:"); | if (type == NULL) | ||||
| next2 = strstr(opts, ",cd:"); | |||||
| if (next == NULL || (next2 != NULL && next2 < next)) | |||||
| next = next2; | |||||
| if (next != NULL) { | |||||
| next[0] = 0; | |||||
| next++; | |||||
| } | |||||
| if (opts[0] == 0) | |||||
| continue; | continue; | ||||
| uopt = strdup(opts); | if (strcmp(type, "hd") == 0) | ||||
| bopt = NULL; | atapi = 0; | ||||
| fp = open_memstream(&bopt, &block_len); | else | ||||
| comma = 0; | atapi = 1; | ||||
| optpos = 0; | |||||
| for (xopts = strtok(uopt, ","); | |||||
| xopts != NULL; | |||||
| xopts = strtok(NULL, ",")) { | |||||
| /* First option assume as block filename. */ | |||||
| if (optpos == 0) { | |||||
| /* | /* | ||||
| * Create an identifier for the backing file. | |||||
| * Use parts of the md5 sum of the filename | |||||
| */ | |||||
| char ident[AHCI_PORT_IDENT]; | |||||
| MD5Init(&mdctx); | |||||
| MD5Update(&mdctx, opts, strlen(opts)); | |||||
| MD5Final(digest, &mdctx); | |||||
| snprintf(ident, AHCI_PORT_IDENT, | |||||
| "BHYVE-%02X%02X-%02X%02X-%02X%02X", | |||||
| digest[0], digest[1], digest[2], digest[3], digest[4], | |||||
| digest[5]); | |||||
| ata_string((uint8_t*)&ata_ident->serial, ident, 20); | |||||
| ata_string((uint8_t*)&ata_ident->revision, "001", 8); | |||||
| if (atapi) { | |||||
| ata_string((uint8_t*)&ata_ident->model, "BHYVE SATA DVD ROM", 40); | |||||
| } | |||||
| else { | |||||
| ata_string((uint8_t*)&ata_ident->model, "BHYVE SATA DISK", 40); | |||||
| } | |||||
| } | |||||
| if ((config = strchr(xopts, '=')) != NULL) { | |||||
| *config++ = '\0'; | |||||
| if (!strcmp("nmrr", xopts)) { | |||||
| ata_ident->media_rotation_rate = atoi(config); | |||||
| } | |||||
| else if (!strcmp("ser", xopts)) { | |||||
| ata_string((uint8_t*)(&ata_ident->serial), config, 20); | |||||
| } | |||||
| else if (!strcmp("rev", xopts)) { | |||||
| ata_string((uint8_t*)(&ata_ident->revision), config, 8); | |||||
| } | |||||
| else if (!strcmp("model", xopts)) { | |||||
| ata_string((uint8_t*)(&ata_ident->model), config, 40); | |||||
| } | |||||
| else { | |||||
| /* Pass all other options to blockif_open. */ | |||||
| *--config = '='; | |||||
| fprintf(fp, "%s%s", comma ? "," : "", xopts); | |||||
| comma = 1; | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* Pass all other options to blockif_open. */ | |||||
| fprintf(fp, "%s%s", comma ? "," : "", xopts); | |||||
| comma = 1; | |||||
| } | |||||
| optpos++; | |||||
| } | |||||
| free(uopt); | |||||
| fclose(fp); | |||||
| DPRINTF("%s\n", bopt); | |||||
| /* | |||||
| * Attempt to open the backing image. Use the PCI slot/func | * Attempt to open the backing image. Use the PCI slot/func | ||||
| * and the port number for the identifier string. | * and the port number for the identifier string. | ||||
| */ | */ | ||||
| snprintf(bident, sizeof(bident), "%d:%d:%d", pi->pi_slot, | snprintf(bident, sizeof(bident), "%d:%d:%d", pi->pi_slot, | ||||
| pi->pi_func, p); | pi->pi_func, p); | ||||
| bctxt = blockif_open(bopt, bident); | |||||
| free(bopt); | |||||
| bctxt = blockif_open(port_nvl, bident); | |||||
| if (bctxt == NULL) { | if (bctxt == NULL) { | ||||
| sc->ports = p; | sc->ports = p; | ||||
| ret = 1; | ret = 1; | ||||
| goto open_fail; | goto open_fail; | ||||
| } | } | ||||
| sc->port[p].bctx = bctxt; | sc->port[p].bctx = bctxt; | ||||
| sc->port[p].pr_sc = sc; | sc->port[p].pr_sc = sc; | ||||
| sc->port[p].port = p; | sc->port[p].port = p; | ||||
| sc->port[p].atapi = atapi; | sc->port[p].atapi = atapi; | ||||
| /* | |||||
| * Create an identifier for the backing file. | |||||
| * Use parts of the md5 sum of the filename | |||||
| */ | |||||
| path = get_config_value_node(port_nvl, "path"); | |||||
| MD5Init(&mdctx); | |||||
| MD5Update(&mdctx, path, strlen(path)); | |||||
| MD5Final(digest, &mdctx); | |||||
| snprintf(ident, AHCI_PORT_IDENT, | |||||
| "BHYVE-%02X%02X-%02X%02X-%02X%02X", | |||||
| digest[0], digest[1], digest[2], digest[3], digest[4], | |||||
| digest[5]); | |||||
| memset(ata_ident, 0, sizeof(struct ata_params)); | |||||
| ata_string((uint8_t*)&ata_ident->serial, ident, 20); | |||||
| ata_string((uint8_t*)&ata_ident->revision, "001", 8); | |||||
| if (atapi) | |||||
| ata_string((uint8_t*)&ata_ident->model, "BHYVE SATA DVD ROM", 40); | |||||
| else | |||||
| ata_string((uint8_t*)&ata_ident->model, "BHYVE SATA DISK", 40); | |||||
| value = get_config_value_node(port_nvl, "nmrr"); | |||||
| if (value != NULL) | |||||
| ata_ident->media_rotation_rate = atoi(value); | |||||
| value = get_config_value_node(port_nvl, "ser"); | |||||
| if (value != NULL) | |||||
| ata_string((uint8_t*)(&ata_ident->serial), value, 20); | |||||
| value = get_config_value_node(port_nvl, "rev"); | |||||
| if (value != NULL) | |||||
| ata_string((uint8_t*)(&ata_ident->revision), value, 8); | |||||
| value = get_config_value_node(port_nvl, "model"); | |||||
| if (value != NULL) | |||||
| ata_string((uint8_t*)(&ata_ident->model), value, 40); | |||||
| ata_identify_init(&sc->port[p], atapi); | ata_identify_init(&sc->port[p], atapi); | ||||
| /* | /* | ||||
| * Allocate blockif request structures and add them | * Allocate blockif request structures and add them | ||||
| * to the free list | * to the free list | ||||
| */ | */ | ||||
| pci_ahci_ioreq_init(&sc->port[p]); | pci_ahci_ioreq_init(&sc->port[p]); | ||||
| Show All 37 Lines | for (p = 0; p < sc->ports; p++) { | ||||
| blockif_close(sc->port[p].bctx); | blockif_close(sc->port[p].bctx); | ||||
| } | } | ||||
| free(sc); | free(sc); | ||||
| } | } | ||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static int | |||||
| pci_ahci_hd_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) | |||||
| { | |||||
| return (pci_ahci_init(ctx, pi, opts, 0)); | |||||
| } | |||||
| static int | |||||
| pci_ahci_atapi_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) | |||||
| { | |||||
| return (pci_ahci_init(ctx, pi, opts, 1)); | |||||
| } | |||||
| #ifdef BHYVE_SNAPSHOT | #ifdef BHYVE_SNAPSHOT | ||||
| static int | static int | ||||
| pci_ahci_snapshot_save_queues(struct ahci_port *port, | pci_ahci_snapshot_save_queues(struct ahci_port *port, | ||||
| struct vm_snapshot_meta *meta) | struct vm_snapshot_meta *meta) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| int idx; | int idx; | ||||
| struct ahci_ioreq *ioreq; | struct ahci_ioreq *ioreq; | ||||
| ▲ Show 20 Lines • Show All 265 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| #endif | #endif | ||||
| /* | /* | ||||
| * Use separate emulation names to distinguish drive and atapi devices | * Use separate emulation names to distinguish drive and atapi devices | ||||
| */ | */ | ||||
| struct pci_devemu pci_de_ahci = { | struct pci_devemu pci_de_ahci = { | ||||
| .pe_emu = "ahci", | .pe_emu = "ahci", | ||||
| .pe_init = pci_ahci_hd_init, | .pe_init = pci_ahci_init, | ||||
| .pe_legacy_config = pci_ahci_legacy_config, | |||||
| .pe_barwrite = pci_ahci_write, | .pe_barwrite = pci_ahci_write, | ||||
| .pe_barread = pci_ahci_read, | .pe_barread = pci_ahci_read, | ||||
| #ifdef BHYVE_SNAPSHOT | #ifdef BHYVE_SNAPSHOT | ||||
| .pe_snapshot = pci_ahci_snapshot, | .pe_snapshot = pci_ahci_snapshot, | ||||
| .pe_pause = pci_ahci_pause, | .pe_pause = pci_ahci_pause, | ||||
| .pe_resume = pci_ahci_resume, | .pe_resume = pci_ahci_resume, | ||||
| #endif | #endif | ||||
| }; | }; | ||||
| PCI_EMUL_SET(pci_de_ahci); | PCI_EMUL_SET(pci_de_ahci); | ||||
| struct pci_devemu pci_de_ahci_hd = { | struct pci_devemu pci_de_ahci_hd = { | ||||
| .pe_emu = "ahci-hd", | .pe_emu = "ahci-hd", | ||||
| .pe_init = pci_ahci_hd_init, | .pe_legacy_config = pci_ahci_hd_legacy_config, | ||||
| .pe_barwrite = pci_ahci_write, | .pe_alias = "ahci", | ||||
| .pe_barread = pci_ahci_read, | |||||
| #ifdef BHYVE_SNAPSHOT | |||||
| .pe_snapshot = pci_ahci_snapshot, | |||||
| .pe_pause = pci_ahci_pause, | |||||
| .pe_resume = pci_ahci_resume, | |||||
| #endif | |||||
| }; | }; | ||||
| PCI_EMUL_SET(pci_de_ahci_hd); | PCI_EMUL_SET(pci_de_ahci_hd); | ||||
| struct pci_devemu pci_de_ahci_cd = { | struct pci_devemu pci_de_ahci_cd = { | ||||
| .pe_emu = "ahci-cd", | .pe_emu = "ahci-cd", | ||||
grehan: Perhaps a flag could be used to indicate a legacy-only device name and retire the -hd and -cd… | |||||
Done Inline ActionsNot quite sure what you mean? Do you want '-s 0,ahci,<path>' to work without an hd: or cd: prefix? Or rather, did I break an existing feature that let you do '-s 0,ahci-cd,hd:<path>'? The current code assumes that ahci-cd and ahci-hd are only used with exactly one port (pci_ahci_hd_legacy_config only creates a single port and doesn't parse a string of drives the way pci_ahci_legacy_config does). jhb: Not quite sure what you mean? Do you want '-s 0,ahci,<path>' to work without an hd: or cd… | |||||
| .pe_init = pci_ahci_atapi_init, | .pe_legacy_config = pci_ahci_cd_legacy_config, | ||||
| .pe_barwrite = pci_ahci_write, | .pe_alias = "ahci", | ||||
| .pe_barread = pci_ahci_read, | |||||
| #ifdef BHYVE_SNAPSHOT | |||||
| .pe_snapshot = pci_ahci_snapshot, | |||||
| .pe_pause = pci_ahci_pause, | |||||
| .pe_resume = pci_ahci_resume, | |||||
| #endif | |||||
| }; | }; | ||||
| PCI_EMUL_SET(pci_de_ahci_cd); | PCI_EMUL_SET(pci_de_ahci_cd); | ||||
Perhaps a flag could be used to indicate a legacy-only device name and retire the -hd and -cd names. It's now possible to create an ahci-cd node with "type=hd".