+static int
+chsc_determine_channel_path_description(int chpid,
+ struct channel_path_desc *desc)
+{
+ int ccode, ret;
+
+ struct {
+ struct chsc_header request;
+ u32 : 24;
+ u32 first_chpid : 8;
+ u32 : 24;
+ u32 last_chpid : 8;
+ u32 zeroes1;
+ struct chsc_header response;
+ u32 zeroes2;
+ struct channel_path_desc desc;
+ } *scpd_area;
+
+ scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scpd_area)
+ return -ENOMEM;
+
+ scpd_area->request.length = 0x0010;
+ scpd_area->request.code = 0x0002;
+
+ scpd_area->first_chpid = chpid;
+ scpd_area->last_chpid = chpid;
+
+ ccode = chsc(scpd_area);
+ if (ccode > 0) {
+ ret = (ccode == 3) ? -ENODEV : -EBUSY;
+ goto out;
+ }
+
+ switch (scpd_area->response.code) {
+ case 0x0001: /* Success. */
+ memcpy(desc, &scpd_area->desc,
+ sizeof(struct channel_path_desc));
+ ret = 0;
+ break;
+ case 0x0003: /* Invalid block. */
+ case 0x0007: /* Invalid format. */
+ case 0x0008: /* Other invalid block. */
+ CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+ ret = -EINVAL;
+ break;
+ case 0x0004: /* Command not provided in model. */
+ CIO_CRW_EVENT(2, "Model does not provide scpd\n");
+ ret = -EOPNOTSUPP;
+ break;
+ default:
+ CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+ scpd_area->response.code);
+ ret = -EIO;
+ }
+out:
+ free_page((unsigned long)scpd_area);
+ return ret;
+}
+
+static void
+chsc_initialize_cmg_chars(struct channel_path *chp, u8 cmcv,
+ struct cmg_chars *chars)
+{
+ switch (chp->cmg) {
+ case 2:
+ case 3:
+ chp->cmg_chars = kmalloc(sizeof(struct cmg_chars),
+ GFP_KERNEL);
+ if (chp->cmg_chars) {
+ int i, mask;
+ struct cmg_chars *cmg_chars;
+
+ cmg_chars = chp->cmg_chars;
+ for (i = 0; i < NR_MEASUREMENT_CHARS; i++) {
+ mask = 0x80 >> (i + 3);
+ if (cmcv & mask)
+ cmg_chars->values[i] = chars->values[i];
+ else
+ cmg_chars->values[i] = 0;
+ }
+ }
+ break;
+ default:
+ /* No cmg-dependent data. */
+ break;
+ }
+}
+
+static int
+chsc_get_channel_measurement_chars(struct channel_path *chp)
+{
+ int ccode, ret;
+
+ struct {
+ struct chsc_header request;
+ u32 : 24;
+ u32 first_chpid : 8;
+ u32 : 24;
+ u32 last_chpid : 8;
+ u32 zeroes1;
+ struct chsc_header response;
+ u32 zeroes2;
+ u32 not_valid : 1;
+ u32 shared : 1;
+ u32 : 22;
+ u32 chpid : 8;
+ u32 cmcv : 5;
+ u32 : 11;
+ u32 cmgq : 8;
+ u32 cmg : 8;
+ u32 zeroes3;
+ u32 data[NR_MEASUREMENT_CHARS];
+ } *scmc_area;
+
+ scmc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!scmc_area)
+ return -ENOMEM;
+
+ scmc_area->request.length = 0x0010;
+ scmc_area->request.code = 0x0022;
+
+ scmc_area->first_chpid = chp->id;
+ scmc_area->last_chpid = chp->id;
+
+ ccode = chsc(scmc_area);
+ if (ccode > 0) {
+ ret = (ccode == 3) ? -ENODEV : -EBUSY;
+ goto out;
+ }
+
+ switch (scmc_area->response.code) {
+ case 0x0001: /* Success. */
+ if (!scmc_area->not_valid) {
+ chp->cmg = scmc_area->cmg;
+ chp->shared = scmc_area->shared;
+ chsc_initialize_cmg_chars(chp, scmc_area->cmcv,
+ (struct cmg_chars *)
+ &scmc_area->data);
+ } else {
+ chp->cmg = -1;
+ chp->shared = -1;
+ }
+ ret = 0;
+ break;
+ case 0x0003: /* Invalid block. */
+ case 0x0007: /* Invalid format. */
+ case 0x0008: /* Invalid bit combination. */
+ CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+ ret = -EINVAL;
+ break;
+ case 0x0004: /* Command not provided. */
+ CIO_CRW_EVENT(2, "Model does not provide scmc\n");
+ ret = -EOPNOTSUPP;
+ break;
+ default:
+ CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+ scmc_area->response.code);
+ ret = -EIO;
+ }
+out:
+ free_page((unsigned long)scmc_area);
+ return ret;
+}
+